运算符重载
Python 关于运算符重载的规则:
- 不能重载内置类型的运算符
- 不能新建,只能重载
- 某些运算符不能重载--is、and、or 和 not
一元运算符
- __neg__
- __pos__
- __invert__
一元运算符是返回新建对象,不能修改self.
加法+
在 Vector 中__add__ 定义我觉得很厉害:
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:
return NotImplemented
def __radd__(self, other):
return self + other
之所以要采取这种方式定义有以下好处:
- Vector 可以和任意长度的 iterable 类型相加
- 返回一个新的 Vector, 不影响 self 或 other
而我们用 NotImplemented 这样更好是因为整个运算的顺序是如下图:
这样我们能最大化得到我们的结果。还需注意: NotImplemented 并不是 NotImplementedError, NotImplemented 是特殊的单例值,而 NotImplementedError 则是异常。
除了__add__
以外,我们最好还用上 __radd__
(反向加法,即交换操作数的加法)。
乘法*
Vector 对于乘法的重载也是类似的:
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
注意这里我们值定义了向量乘以一个数的乘法。作者也尝试给我们做点乘,我也才知道矩阵乘法被分配给了@符号,我们也可以做这个定义,然后来运算
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 # this only works in Python 3.5
比较运算
甚至我发现比较运算都和加法的调用过程类似,当我们比较 Vector 和 Vector2d 的时候,虽然第二个操作数不是 Vector,得到了 NotImplemented,然后我们又调用 Vector2d 的比较操作,此时两个被变成元组,然后就得到了相等的结果。
增量赋值运算
如果我们没有添加 __iadd__
, 我们当然也能使用 a += b,但这里是语法糖,相当于 a = a + b ,如果我们考察 a 的 id,还是会发现它已经变了。如果我们实现了__iadd__
,当然我们实现的时候就应当就地修改左操作数,而不生成新的对象。
当然对于不可变类型,就一定不能实现这种方法。
import itertools # 先导入标准库模块
from tombola import Tombola
from bingo import BingoCage
class AddableBingoCage(BingoCage): # 扩展自BingoCage
def __add__(self, other):
if isinstance(other, Tombola): # add操作数第二个只能是 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) #如果是别的类,用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) # 重载入 self
return self # 增量赋值特殊方法必须返回self
这个例子告诉了我们几点:
- iadd 比 add 更宽容
- 一般如果中缀运算符的正向方法的other与self属于同类型,则没必要定义反向方法,反向更多的是为了处理类型不同的操作数