微前端架构如何改变企业的开发模式与效率提升
1137
2022-09-30
系统学习Python——类(class)代码的编写基础与实例:类可以截获Python运算符
分类目录:《系统学习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对象的*运算符总是创建新对象。实践证明,重载的运算符应该以与内置的运算符实现同样的方式工作。因为运算符重载其实只是一种表达式对方法的分发机制,所以你可以在自己的类对象中以任何喜欢的方式解释运算符。
为什么要使用运算符重载
作为一名类的设计者,你可以选择是否要使用运算符重载。你的决定取决于有多想让对象的用法和外观看起来更像内置类型。就像前面提到的那样,如果省略运算符重载方法而且不从父类中继承该方法,那么实例就不支持相应的运算:如果试着使用这个实例进行运算,就会引发异常(或者在一些类似打印的情形下,使用标准的默认运算符重载方法)。坦率地讲,只有在实现具有数学本质的对象时,才会用到许多运算符重载方法。例如,向量或矩阵类可以重载加法运算符,但员工类可能就不用。就较简单的类而言,可能根本不会用到重载,因此应该利用显式的方法调用来实现对象的行为。
另外,如果需要把用户定义的对象传入预期接受并使用内置类型(例如列表或字典)可用的运算符的函数,那么你可能就会决定使用运算符重载。在类中实现同一组运算符,可以保证对象能支持相同的预期的对象接口,因此会与这个函数兼容。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~