支持一元运算符很简单,只需实现相应的特殊方法。这些特殊方法只有一个参数,self。然后,使用符合所在类的逻辑实现。不过,要遵守运算符的一个基本规则:始终返回一个新对象。也就是说,不能修改self,要创建并返回合适类型的新实例
# 在vector.py代码中添加如下代码支持一元运算符
def __neg__(self):
return Vector(-x for x in self)
def __pos__(self):
return Vector(self)
目标结果
>>> 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
# 两个不同长度的向量相加
>>> v1 = Vector([3, 4, 5, 6])
>>> v3 = Vector([1, 2])
>>> v1 + v3
Vector([4.0, 6.0, 5.0, 6.0])
特殊方法实现
# 在Vector类中定义
def __add__(self, other):
try:
pairs = itertools.zip_longest(self, other, fillvalue=0.0)
return Vector(a + b for a, b in pairs)
except TypeError:
"""
如果中缀运算符方法抛出异常,就终止了运算符分派机制。
对TypeError 来说,通常最好将其捕获,然后返回NotImplemented。
这样,解释器会尝试调用反向运算符方法,如果操作数是不同的类型,
对调之后,11反向运算符方法可能会正确计算。
"""
return NotImplemented
"""为了让混合类型加法能正确计算,我们要实现Vector.__radd__方法。
这是一种后备机制,如果左操作数没有实现__add__ 方法,或者实现了,
但是返回NotImplemented 表明它不知道如何处理右操作数,那么Python 会
调用__radd__ 方法
"""
def __radd__(self, other):
return self + other
class Vector:
type code = 'd'
def __init__(self, components):
self._components = array(self.typecode, components)
def __mul__(self, scalar):
"""如果scalar 是numbers.Real 某个子类的实例,
用分量的乘积创建一个新Vector 实例"""
if isinstance(scalar, numbers.Real):
return Vector(n * scalar for n in self)
# 否则,返回NotImplemented,让Python 尝试在scalar 操作数上调用__rmul__ 方法
else:
return NotImplemented
# 这里,__rmul__ 方法只需执行self * scalar,委托给__mul__ 方法
def __rmul__(self, scalar):
return self * scalar
python3.5中还引入了点积运算符@
class Vector:
def __matmul__(self, other):
try:
return sum(a * b for a, b in zip(self, other))
except TypeError:
return NotImplemented
def __rmatmul__(self, other):
return self @ other
>>> va = Vector([1, 2, 3])
>>> vz = Vector([5, 6, 7])
>>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7
True
>>> [10, 20, 30] @ vz
380.0
>>> va @ 3
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for @: 'Vector' and 'int'
常见的比较运算符有(==, !=, >, <, >=, <=)
中缀运算符 | 正方向调用 | 反方向调用 | 后备机制 |
---|---|---|---|
a==b | a.__eq__(b) | b.__eq__(a) | 返回id(a) == id(b) |
a!=b | a.__ne__(b) | b.__ne__(a) | 返回not(a==b) |
a>b | a.__gt__(b) | b.__lt__(a) | 抛出TypeError |
a | a.__lt__(b) | b.__gt__(a) | 抛出TypeError |
a>=b | a.__ge__(b) | b.__le__(a) | 抛出TypeError |
a<=b | a.__le__(b) | b.__ge__(a) | 抛出TypeError |
看看Vector类中的__eq__方法和__ne__方法
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
"""那么!= 运算符呢?我们不用实现它,因为从object 继承的__ne__ 方法的
后备行为满足了我们的需求:定义了__eq__ 方法,而且它不返回NotImplemented,
__ne__ 会对__eq__ 返回的结果取反。
"""
def __ne__(self, other):
eq_result = self == other
if eq_result is NotImplemented:
return NotImplemented
else:
return not eq_result
对于Python3而言,定义了__eq__方法, 就不需要再定义__ne__方法, 因为从object继承的__ne__方法足够用了,几乎不用重载
增量赋值不会修改不可变目标,而是新建实例,然后重新绑定
>>> v1 = Vector([1, 2, 3])
>>> v1_alias = v1
>>> id(v1)
4302860128
# 增量加法创建了心得实例
>>> v1 += Vector([4, 5, 6])
>>> v1
Vector([5.0, 7.0, 9.0])
>>> id(v1)
4302859904
# v1_alias没被修改
>>> v1_alias
Vector([1.0, 2.0, 3.0])
# 运算与预期相符,但创建了新的Vector实例
>>> v1 *= 11 #
>>> v1
Vector([55.0, 77.0, 99.0])
>>> id(v1)
4302858336
我们发现,Python吃力运算符的方式是把它们当常规的运算符加上赋值操作,即a+=b, 其实会当成a = a + b 处理。这样会始终创建新对象,因此可变类型和不可变类型都能用。对于可变对象来说,可以实现就地特殊方法, 例如支持+=的__iadd__方法,然后修改左操作数的值