admin管理员组文章数量:1666584
分类目录:《系统学习Python》总目录
现在,让我们来看类和模块的第三个也是最后一个主要差别:运算符重载。简而言之,运算符重载就是让用类编写的对象,可截获并响应用在内置类型上的运算:加法、切片、打印和点号运算等。这其实只是一种自动分发机制:表达式和其他内置运算被路由到了类内部的实现。在这点上类和模块也基本不同:模块可以实现函数调用,但却不能实现表达式的行为。
虽然我们可以把所有类的行为实现为方法函数,运算符重载则让对象和Python的对象模型更紧密地结合起来。此外,因为运算符重载能让我们自己的对象拥有内置对象那样的行为,所以这既可以让对象接口更为一致并且更易于学习,又可以让类对象被预期内置类型接口的代码来处理。以下是重载运算符主要概念的概要:
- 以双下划线命名的方法
__X__
是特殊钩子:在Python类中我们实现运算符重载是通过提供特殊命名的方法来拦截运算。Python语言在每种运算和特殊命名的方法之间,定义了固定不变的映射关系。 - 当实例出现在内置运算中时,这类方法会自动被调用:例如,如果实例对象继承了一个
__add__
方法,那么当对象出现在+
表达式内时,该方法就会被调用。而该方法的返回值将作为相应表达式的结果。 - 类可以重载绝大多数内置类型运算:Python中有几十种特殊运算符重载的方法的名称,几乎可截获并实现内置类型的所有运算。它不仅包括了表达式,同时还包括像打印和对象创建这样的基础运算。
- 默认的运算符重载方法既不存在,也不需要:如果类没有定义或继承运算符重载法,那么类的实例将不能支持相应的运算。例如,如果没有
__add__
,+
表达式就会引发异常。 - 新式类有一些默认的运算符重载方法,但是不属于常见运算:在Python所谓新式类中,一个名为
object
的根类确实提供了某些默认的__X__
方法。但是提供的不多,同时也不属于大多数常见的运算 - 运算符将类与Python的对象模型结合到一起:通过重载类型运算,我们可以让采用类实现的用户定义对象获得与内置对象一样的行为,因此这保证了与预期接口的一致性和兼容性。
运算符重载是可选的功能。它主要被Python工具开发人员使用,而不是那些应用程序开发人员。此外,坦率地说,不应该因为运算符重载看起来很聪明或是很“酷”就随意去使用。除非类需要模仿内置类型接口,否则你应该使用更简单的命名方法。例如,员工数据库应用程序为什么要支持像*
和+
这类表达式呢?通常来说,有着像giveRaise
和promote
这样的名称的方法是更有意义的。
因此,我们不会在本文中深入讨论Python中每一个可用的运算符重载方法。不过,有一个运算符重载方法你可能会在每个实际的Python类中都遇到:__init__
方法,也称为构造函数方法,它是用于初始化对象的状态的。你应该特别注意这个方法,因为__init__
和self
参数是阅读和理解Python的OOP程序代码的关键之一。
下面是另一个例子,我们要定义《类(class)代码的编写基础与实例:类通过继承进行定制》的SecondClass
的子类,实现Python会自动调用的三个特殊名称属性:
__init__
会在创建新的实例时被调用:self
是新的ThirdClass
对象__add__
会在ThirdClass
实例出现在+
表达式中时被调用。__str__
会在打印一个对象的时候。从技术上讲,当通过__str__
内置函数或者其Python内部的等价形式来将对象转换为打印字符串的时候被调用。
我们先定义ThirdClass
类:
class ThirdClass(SecondClass):
def __init__(self, value):
self.data = value
def __add__(self, other):
return ThirdClass(self.data + other)
def __str__(self):
return 'ThirdClass: %s' % self.data
def mul(self, other):
self.data *= other
然后初始化一个类实例对象并执行一些操作:
a = ThirdClass('hy592070616')
a.display()
print(a)
输出:
Blog="hy592070616"
ThirdClass: hy592070616
然后通过重载的内置方法__add__
实现+
的运算:
b = a + ' MachineLearning'
b.display()
print(b)
输出:
Blog="hy592070616 MachineLearning"
ThirdClass: hy592070616 MachineLearning
测试ThirdClass
类的其它方法:
a.mul(3)
print(a)
输出:
ThirdClass: hy592070616hy592070616hy592070616
ThirdClass
是一个SecondClass
对象,所以其实例会继承《类(class)代码的编写基础与实例:类通过继承进行定制》的SecondClass
的display
方法。但是,ThirdClass
生成的调用现在会传人一个参数(例如:abc
),这是传给__init__
构造函数内的参数value
的,并在构造函数中被赋值给self.data
。最终的效果是ThirdClass
会在创建时自动设置data
属性,而不再是必须在构建之后通过setdata
调用。
此外,ThirdClass
对象现在可以出现在+
表达式和print
调用中。对于+
,Python把左侧的实例对象传给__add__
中的self
参数,而把右侧的对象传给other
,如下图所示。而__add__
返回的内容则成为+
表达式的结果。对于print
,Python把要打印的对象传给__str__
中的self
,该方法返回的字符串看作对象的打印字符串。我们可以用一个常规的print
来显示该类的对象,而不是调用特殊的display
方法。
__init__
,__add__
和__str__
这样的特殊命名方法会由子类和实例继承,就像一个class
语句中被赋值的其他名称。如果这些名称没有被编写在类中,那么Python就会在该类的所有父类中寻找这类变量名。运算符重载方法的名称并不是内置变量或保留字,它们只是当对象出现在不同的上下文时Python会自动去搜索的属性。Python通常会自动进行调用,但偶尔也能被你所编写的程序代码调用。
是否返回结果
一些像__str__
的运算符重载方法要求结果,但另一些则更加灵活。例如,注意,__add__
方法是如何通过结果值调用ThirdClass
,从而创建并返回一个该类新的实例对象的。也就是说,这反过来会触发__add__
将结果初始化。这是一个常见的约定,也解释了为什么程序清单中的b
有一个display
方法;因为在该类的对象上调用+
会返回一个新的该类对象,所以b
也是一个ThirdClass
对象。这本质上传播了该类型。
相比之下,mul
会在原位置修改当前的实例对象(通过重新赋值self
属性)。我们可以通过重载*
表达式来实现mul
,但这将与内置的数字与字符串的行为极其不同,因为这里ThirdClass
对象的*
运算符总是创建新对象。实践证明,重载的运算符应该以与内置的运算符实现同样的方式工作。因为运算符重载其实只是一种表达式对方法的分发机制,所以你可以在自己的类对象中以任何喜欢的方式解释运算符。
为什么要使用运算符重载
作为一名类的设计者,你可以选择是否要使用运算符重载。你的决定取决于有多想让对象的用法和外观看起来更像内置类型。就像前面提到的那样,如果省略运算符重载方法而且不从父类中继承该方法,那么实例就不支持相应的运算:如果试着使用这个实例进行运算,就会引发异常(或者在一些类似打印的情形下,使用标准的默认运算符重载方法)。坦率地讲,只有在实现具有数学本质的对象时,才会用到许多运算符重载方法。例如,向量或矩阵类可以重载加法运算符,但员工类可能就不用。就较简单的类而言,可能根本不会用到重载,因此应该利用显式的方法调用来实现对象的行为。
另外,如果需要把用户定义的对象传入预期接受并使用内置类型(例如列表或字典)可用的运算符的函数,那么你可能就会决定使用运算符重载。在类中实现同一组运算符,可以保证对象能支持相同的预期的对象接口,因此会与这个函数兼容。
这里我们会经常使用的一个重载方法是__init__
构造函数,它用来初始化新建的实例对象,并出现在几乎每一个实际的类中。因为__init__
可以让类在新产生的实例中立即添加属性,所以对每种你将写的类而言构造函数都是有用的。事实上,虽然Python不会对实例的属性进行声明,但你通常也可以通过阅读类的__init__
方法的代码来看看实例有哪些属性。当然,尝试一下好玩的语言工具是无可厚非的,但它们并不总是能转化为产品代码。假以时日和经验,我们将会自然且熟练地使用这些编程模式和准则。
版权声明:本文标题:系统学习Python——类(class)代码的编写基础与实例:类可以截获Python运算符 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1730075086a1221668.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论