运算符重载的作用是让用户定义的对象使用中缀运算符(如+和|)和一元运算符(如-和~)。
在Python中,这些也算是运算符:
函数调用:()
属性访问:.
元素访问和切片:[]
运算符重载基础
在某些圈子中,运算符重载的名声不好,比如Java之父说过:我决定不支持运算符重载,这完全是个人选择,因为我见过太多C++程序员滥用它。
但是如果使用得当,API会变得更好用,代码变的I易于阅读。
Python施加了一些限制,做好了灵活性、可用性和安全性方面的平衡:
不能重载内置类型的运算符
不能新建运算符,只能重载现有的
某些运算符不能重载:is、and、or、not (但是位运算符&、|、~可以)
下面依次介绍:一元运算符、中缀运算符、比较运算符、增量赋值运算符。
一元运算符
- (__neg__) 一元取负运算符。如果x是-2,那么-x == 2
+ (__pos__)一元取正运算符。通常x == +x,也有一些例外
~ (__invert__)对整数按位取反。定义为~x == -(x+1) ,如果x是2,那么~x ==-3
abs (__abs__) 取绝对值
要实现一元运算符,就实现括号中对应的特殊方法,这些特殊方法都只有一个参数self。要遵守一个原则:始终返回一个新的对象,也就是说不能修改self,要创建并返回合适类型的新实例。对于+和-来说,返回的结果可能是和self同一类型的实例;对于abs来说结果应该是一个标量;但是对于~来说,很难说返回什么是合理的,因为可能不是处理整数的位,例如在ORM中,SQL where子句应该返回反集。
示例,Vector类中实现了__abs__方法,补充上__neg__和__pos__方法
def __abs__(self):
"""绝对值 (向量取模)"""
return math.sqrt(sum(x * x for x in self.__components)) # math.sqrt()求平方根。先用sum求每个分量的平方之和,然后开平方
def __neg__(self):
"""取负值,构建新的实例,每个分量都取反"""
return Vector(-x for x in self)
def __pos__(self):
"""取正值,构建新的实例,传入self各个分量"""
return Vector(self)
补充下在什么时候x和+x不相等:
在Python中几乎所有情况下x == +x,但是在标准库中也有例外的情况,有两例
第一例是decimal.Decimal有关,这是因为使用+时,精度变了
import decimal
ctx = decimal.getcontext() # 获取当前全局算术运算的上下文引用
ctx.prec = 40 # 把算术运算上下文精度设为40
one_third = decimal.Decimal('1') / decimal.Decimal('3')
print(one_third)
print(+one_third)
print(one_third == +one_third) # 比较
ctx.prec = 28 # 把算术运算上下文精度设为28
print(one_third)
print(+one_third)
print(one_third == +one_third) # 比较
打印
0.3333333333333333333333333333333333333333
0.3333333333333333333333333333333333333333
True
0.3333333333333333333333333333333333333333
0.3333333333333333333333333333
False
上面可以看到,在改变上下文精度后,再次与+one_third比较时就不相等了,因为+运算符会返回一个新的实例对象,而这个实例对象是根据当时的上下文精度创建的。
第二例是collection.Counter有关,Counter类实现了几个算术运算符,例如中缀运算符+的作用是把两个Counter计数器加到一起,两边相加时,负值和零值可能会改变。
而对于一元运算符+等同于加上一个空的Counter,因此他产生一个新的Counter且仅保留大于零的计数器。
import collections
ct =