1 普通运算符的重载
运算符重载并不是一个很简单的话题,但大多数的特殊情况并不实用,本文只讨论较常用的运算符重载。首先,Python中的各种数学运算,包括加减乘除等,都对应着某个方法调用。如1 + 2实际上对应着形如1.__add__(2)的调用。所以,在程序中,用户也可以修改这些函数,从而自定义这些运算符号的行为。下面以__add__与__radd__为例,讨论这样的操作。
当一个类重写了__add__方法后,就可以对类实例使用加号运算,__add__方法应返回一个运算后的结果:
class Test:
def __add__(self, other):
return 1 + other
print(Test() + 10)
__add__方法有两个形参,分别对应于加号前后的两个值,所以上例中的__add__只是简单的把加号后值加1的结果返回,输出为11。
__radd__方法不太常用,这里仅作简要说明。当执行加法运算时,实际上是以self.__add__(other)的形式调用了加号左值的__add__方法,而如果由于加号两边对象不一致(如numpy中的ndarray可以与Python list进行运算),加号左值并没有定义__add__方法时,此时就会颠倒加号的左右值,并尝试调用加号右值的__radd__方法。例如上文中的Test() + 10,如果换成10 + Test(),则调用将失败,因为int类型不能以Test()作为第二参数进行__add__方法调用。此时,如果Test类还定义了__radd__方法:
class Test:
def __add__(self, other):
return 1 + other
def __radd__(self, other):
return 1 + other
则此时如果进行10 + Test(),由于10.__add__(Test())将调用失败,那么就会尝试Test().__radd__(10),颠倒左右参数,并调用加号右值的__radd__方法。故大部分情况下,__add__和__radd__方法的定义都是类似的。在实际情况中,如果考虑到可能存在混合类型运算时,则可能需要考虑再定义__radd__方法。
2 增强赋值运算符的重载
增强赋值运算在很多编程语言中都有体现,其是一类由运算符加上等号组成的自身运算符。此类运算符同时执行运算与赋值操作,即对self本身进行运算与赋值。增强赋值运算符与普通运算符在重载时唯一的区别在于:普通运算符返回的是运算结果,而增强赋值运算符返回的一定是self,这个self的值已经通过运算而改变,返回的self将通过赋值覆盖掉原先的self。下面以__iadd__方法为例讨论增强赋值运算符的重载:
class Test:
def __init__(self, num):
self.num = num
def __add__(self, value):
return self.num + value
def __iadd__(self, value):
self.num += value
return self
testObj = Test(2)
testObj += 3
print(testObj.num)
上例中,__iadd__直接对self进行运算操作,改变了self中的某些值,然后返回self本身。所以返回的这个self在增强赋值运算之后会直接覆盖掉运算符左值,从而实现自身值的改变。而对比__add__方法,其仅仅返回了一个加法运算的结果,并不改变self本身。
3 __repr__与__str__的重载
__repr__与__str__分别对应着Python中的repr与str函数调用。关于这两个函数的区别,一般上认为:repr是生成“适合解释器阅读的格式”,而str则生成“适合用户阅读的格式”,这样的解释很模糊,且在实际使用中并不需要特别关注这两个函数的功能。因为绝大多数情况下,用户一般都只会调用str函数而非repr函数,且print函数的输出样式也与str函数的返回值一致,故以下主要针对__str__方法进行讨论。
在用户自定义的类型中,__str__方法一般只与print函数连用,起到明确、美化输出的作用。默认情况下,如果不定义__str__方法,则输出某个类实例时会显示“”这样的字符串,这就是print函数对当前对象调用__str__方法的结果。而如果需要美化输出,就可以重写__str__方法,返回一个期望的字符串:
class Test:
def __init__(self, num):
self.num = num
def __str__(self):
return '[%d]' % self.num
print(Test(2))
上例中,通过重载__str__方法,使得print(Test(2))时,不再输出“”这样的字符串,而是输出了自定义的“[2]”字符串。
由于一般情况下均不关心__str__与__repr__的区别,故在重载__str__方法后,往往只需要将__repr__方法也绑定到__str__方法上即可:
__repr__ = __str__
同理,重载__repr__方法,然后__str__ = __repr__,也是可以的。
4 __call__的重载
__call__方法的重载应用场合较少,本文只做简要讨论。
首先,Python中针对一个对象,可以使用多种“符号后缀语法”,这些语法背后都分别对应着一个或一系列的方法调用。如一个对象后接“[...]”,则对应着__*item__方法系列;后接“.”,则对应着__*attr__与__getattribute__方法系列;而如果一个对象后接“(...)”,则对应着代表函数调用的__call__方法。
当重载一个类的__call__方法后,这个类的对象就可以当做一个函数一样被调用,也可为__call__方法定义形参,则实际调用中就可以传入相对应的实参:
class Test:
def __init__(self, num):
self.num = num
def __call__(self, value):
print(self.num, value)
Test(2)('call')
当__call__被定义后,Test(2)作为一个类实例,就可以当做函数一样调用,并可传入相应的参数。上述代码的调用结果即为输出“2 call”。
2018年6月于苏州