Python正确重载运算符的方法示例详解
前言
说到运算符重载相信大家都不陌生,运算符重载的作用是让用户定义的对象使用中缀运算符(如+和|)或一元运算符(如-和~)。说得宽泛一些,在Python中,函数调用(())、属性访问(.)和元素访问/切片([])也是运算符。
我们为Vector类简略实现了几个运算符。__add__和__mul__方法是为了展示如何使用特殊方法重载运算符,不过有些小问题被我们忽视了。此外,我们定义的Vector2d.__eq__方法认为Vector(3,4)==[3,4]是真的(True),这可能并不合理。下面来一起看看详细的介绍吧。
运算符重载基础
在某些圈子中,运算符重载的名声并不好。这个语言特性可能(已经)被滥用,让程序员困惑,导致缺陷和意料之外的性能瓶颈。但是,如果使用得当,API会变得好用,代码会变得易于阅读。Python施加了一些限制,做好了灵活性、可用性和安全性方面的平衡:
- 不能重载内置类型的运算符
- 不能新建运算符,只能重载现有的
- 某些运算符不能重载——is、and、or和not(不过位运算符
- &、|和~可以)
前面的博文已经为Vector定义了一个中缀运算符,即==,这个运算符由__eq__方法支持。我们将改进__eq__方法的实现,更好地处理不是Vector实例的操作数。然而,在运算符重载方面,众多比较运算符(==、!=、>、<、>=、<=)是特例,因此我们首先将在Vector中重载四个算术运算符:一元运算符-和+,以及中缀运算符+和*。
一元运算符
-(__neg__)
一元取负算术运算符。如果x是-2,那么-x==2。
+(__pos__)
一元取正算术运算符。通常,x==+x,但也有一些例外。如果好奇,请阅读“x和+x何时不相等”附注栏。
~(__invert__)
对整数按位取反,定义为~x==-(x+1)。如果x是2,那么~x==-3。
支持一元运算符很简单,只需实现相应的特殊方法。这些特殊方法只有一个参数,self。然后,使用符合所在类的逻辑实现。不过,要遵守运算符的一个基本规则:始终返回一个新对象。也就是说,不能修改self,要创建并返回合适类型的新实例。
对-和+来说,结果可能是与self同属一类的实例。多数时候,+最好返回self的副本。abs(...)的结果应该是一个标量。但是对~来说,很难说什么结果是合理的,因为可能不是处理整数的位,例如在ORM中,SQLWHERE子句应该返回反集。
def__abs__(self): returnmath.sqrt(sum(x*xforxinself)) def__neg__(self): returnVector(-xforxinself)#为了计算-v,构建一个新Vector实例,把self的每个分量都取反 def__pos__(self): returnVector(self)#为了计算+v,构建一个新Vector实例,传入self的各个分量
x和+x何时不相等
每个人都觉得x==+x,而且在Python中,几乎所有情况下都是这样。但是,我在标准库中找到两例x!=+x的情况。
第一例与decimal.Decimal类有关。如果x是Decimal实例,在算术运算的上下文中创建,然后在不同的上下文中计算+x,那么x!=+x。例如,x所在的上下文使用某个精度,而计算+x时,精度变了,例如下面的