13.1 运算符重载基础

Python 在重载运算符上施加了一些限制,做好了灵活性、可用性和安全性的平衡:

  • 不能重载内置类型的运算符
  • 不能新建运算符,只能重载现有的
  • 某些运算符不能重载:isandornot

13.2 一元运算符

支持一元运算符很简单,只需实现相应的特殊方法。这些特殊方法只有一个参数,self。然后,使用符合所在类的逻辑实现。不过,要遵守运算符的一个基本规则:始终返回一个新对象。也就是说,不能修改 self,要创建并返回合适类型的新实例。

为了支持涉及不同类型的运算,Python 为中缀运算符特殊方法提供了特殊的分派机制。对表达式 a + b 来说,解释器会执行以下几步操作:

  1. 如果 a 有 __add__ 方法,而且返回值不是 NotImplemented,调用 a.__add__(b),然后返回结果。
  2. 如果 a 没有 __add__ 方法,或者调用 __add__ 方法返回 NotImplemented,检查 b 有没有 __radd__ 方法,如果有,而且没有 返回 NotImplemented,调用 b.__radd__(a),然后返回结果。
  3. 如果 b 没有 __radd__ 方法,或者调用 __radd__ 方法返回 NotImplemented,抛出 TypeError,并在错误消息中指明操作数类型 不支持。

在重载运算符时,如果我们在进行处理的过程中抛出了异常,那么我们不应该直接让该异常抛出,因为这样有可能阻断 Python 继续尝试调用其他运算符的流程,所以我们应该将错误捕获,并返回 Notimplemented

1
2
3
4
5
6
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

如果操作数的类型不同,我们要检测出不能处理的操作数。可以使用两种方式处理这个问题:一种是鸭子类型,直接尝试执行运算,如果有问题,捕获 TypeError 异常;另一种是显式使用 isinstance 测试,但是不能测试具体类,而要测试例如 numbers.Real 这样的抽象基类。

13.6 增量赋值运算符

如果一个类没有实现就地运算符,增量赋值运算符只是语法糖:a += b 的作用与 a = a + b 完全一样。

然而,如果实现了就地运算符方法,例如 __iadd__,计算 a += b 的结果时会调用就地运算符方法。这种运算符的名称表明,它们会就地修改左操作数,而不会创建新对象作为结果。

对于不可变类型,一定不能实现就地特殊方法。