运算符重载的作用是让用户定义的对象使用中缀运算符(如+和|)或一元运算符(如-和~)。
13.1 运算符重载基础
Python对运算符重载做了一些限制:
- 不能重载内置类型的运算符
- 不能新建运算符,只能重载现有的
- 某些运算符不能重载——is、and、or 和 not
13.2 一元运算符
- (__neg__) 取负运算符。如果x 是-2,那么-x == 2
+ (__pos__) 取正运算符
~ (__invert__) 对整数按位取反
支持一元运算符很简单,只需实现相应的特殊方法。这些特殊方法只有一个参数,self。然后,使用符合所在类的逻辑实现。不过,哟啊遵守运算符的一个基本规则:始终返回一个新对象。
示例13-1 vector_v6.py:新增一元运算符 - 和 +
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
def __neg__(self):
return Vector(-x for x in self)
def __pos__(self):
return Vector(self)
13.3 重载向量加法运算符+
两个欧几里得向量加在一起得到的是一个新向量,它的各个分量是两个向量中相应的分量之后。
示例13-4 Vector.__add__方法
def __add__(self, other):
pairs = itertools.zip_longest(self, other, fillvalue=0)
return Vector(a+b for a, b in pairs)
from vector_v6 import Vector
v1 = Vector([3,4,5])
v2 = Vector([6,7,8])
v1 + v2
Vector([9.0, 11.0, 13.0])
v1 + v2 == Vector([3+6, 4+7, 5+8])
True
示例13-5 第一版Vector.__add__方法也支持Vector之外的对象
from vector2d_v3 import Vector2d
v1 = Vector([3,4,5])
v1 + (10, 20, 30)
Vector([13.0, 24.0, 35.0])
v2d = Vector2d(1, 2)
v1 + v2d
Vector([4.0, 6.0, 5.0])
示例13-6 如果左操作数是Vector之外的对象,第一版Vector.__add__方法无法处理
v1 = Vector([3, 4, 5])
(10, 20, 30) + v1
Traceback (most recent call last):
...
TypeError: can only concatenate tuple (not "Vector") to tuple
from vector2d_v3 import Vector2d
v2d = Vector2d(1, 2)
v2d + v1
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'Vector2d' and 'Vector'
为了支持涉及不同类型的运算,Python为中缀运算符特殊方法提供了特殊的分派机制。对表达式a+b来说,解释器会执行以下几步
__radd__是__add__的“反射”版本或“反向”版本。
示例13-7 Vector.__add__和__radd__方法
def __add__(self, other):
pairs = itertools.zip_longest(self, other, fillvalue=0)
return Vector(a+b for a, b in pairs)
def __radd__(self, other):
return self + other
示例13-8 Vector.__add__方法的操作数要是可迭代对象
v1 = Vector([3, 4, 5])
v1 + 1
Traceback (most recent call last):
File "<input>", line 1, in <module>
...
pairs = itertools.zip_longest(self, other, fillvalue=0)
TypeError: zip_longest argument #2 must support iteration
如果由于类型不兼容而导致运算符特殊方法无法返回有效的结果,那么应该返回NotImplemented,而不是抛出TypeError。返回NotImplemented时,另一个操作数所属的类型还有机会执行运算,即Python会尝试调用反向方法。
示例13-10: 修改__add__方法
def __add__(self, other):
try:
pairs = itertools.zip_longest(self, other, fillvalue=0)
return Vector(a+b for a, b in pairs)
except TypeError:
return NotImplemented
13.4 重载标量乘法运算符*
先实现最简单的__mul__和__rmul__
def __mul__(self, scalar):
return Vector(n * scalar for n in self)
def __rmul__(self, scalar):
return self * scalar
这两个方法确实可用,但是提供不兼容的操作数时会出问题。
示例13-11 vector_v7.py: 增加*运算符方法
def __mul__(self, scalar):
if isinstance(scalar, numbers.Real):
return Vector(n * scalar for n in self)
else:
return NotImplemented
def __rmul__(self, scalar):
return self * scalar
from vector_v7 import Vector
v1 = Vector([1.0, 2.0, 3.0])
14 * v1
Vector([14.0, 28.0, 42.0])
v1 * True
Vector([1.0, 2.0, 3.0])
from fractions import Fraction
v1 * Fraction(1, 3)
Vector([0.3333333333333333, 0.6666666666666666, 1.0])
13.5 众多比较运算符
Python解释器对众多比较运算符(==、 !=、 <、 >、 >=、 <=)的处理与前文类似,不过在两个方面有重大区别。
- 正向和反向调用使用同一系列方法。
- 对== 和!=来说,如果反向调用失败,Python会比较对象的ID,而不抛出TypeError。
示例13-12
va = Vector([1.0, 2.0, 3.0])
vb = Vector(range(1, 4))
va == vb
True
vc = Vector([1, 2])
t3 = (1,2,3)
va == t3
True
最后一个结果可能不是很理想,我们改进下:
示例13-13 改进Vector类的__eq__方法
def __eq__(self, other):
if isinstance(other, Vector):
return len(self) == len(other) and \
all(a == b for a, b in zip(self, other))
else:
return NotImplemented
13.6 增量赋值运算符
Vector类已经支持增量赋值运算符+= 和 *=
示例13-5 增量运算符不会修改不可变目标,而是新建实例
v1 = Vector([1, 2, 3])
v1_alias = v1
id(v1)
140727697662584
v1 += Vector([4, 5, 6])
v1
Vector([5.0, 7.0, 9.0])
id(v1)
140727697662864
v1_alias
Vector([1.0, 2.0, 3.0])
v1 *= 11
v1
Vector([55.0, 77.0, 99.0])
id(v1)
140727697662472
如果一个类没有实现__iadd__就地加法运算符,增量赋值运算符只是语法糖:a += b的作用与a = a + b完全一样。
然而如果实现了就地运算符方法,例如__iadd__,计算 a + b的结果时会调用就地运算符方法。这种运算符的名称表明,它们会就地修改左操作数,而不会创建新对象作为结果。
示例13-18 bingoaddable.py: AddableBingoCage扩展BingoCage,支持+和+=
def __add__(self, other):
if isinstance(other, Tombola):
return AddableBingoCage(self.inspect() + other.inspect())
else:
return NotImplemented
def __iadd__(self, other):
if isinstance(other, Tombola):
other_iterable = other.inspect()
else:
try:
other_iterable = iter(other)
except TypeError:
self_cls = type(self).__name__
msg = "right operand in += must be {!r} or an iterable"
raise TypeError(msg.format(self_cls))
self.load(other_iterable)
return self