13.1 运算符重载基础

  • Python 的运算符重载限制:不能重载内置类型运算符、只能重载现有的某些运算符。

13.2 一元运算符

  • 常见的一元运算符:取负 -, __neg__、取正 +, __pos__、按位取反 ~, __invert__、绝对值 abs(), __abs__
  • 运算符的基本规则:始终返回一个新对象
  • x+x 不一定相等,如 decimal 模块中不同的上下文精度下会计算得到不同的数据。

13.3 重载加法运算符 +

  • Python 为中缀运算符特殊方法提供了特殊的分派机制。对于 a + b
    • 如果 a__add__ 方法且不返回 NotImplemented,则调用它;
    • 如果 b__radd__ 方法(反向特殊方法)且不返回 NotImplemented,则调用它
    • 否则抛出 TypeError
  • NotImplemented 不是异常,是特殊的返回值。NotImplementedError 是异常,用于提示抽象基类的方法没有实现;
  • 在实现运算符的过程中,为了遵守鸭子类型精神,通过捕获异常的方式来判断操作数类型,而非类型检测。且在无法处理计算时,需要返回 NotImplement,而非抛出异常。

13.4 重载标量乘法运算符 *

  • 需要对数字的类型(如实复数)进行判断时,使用白鹅类型进行判断更合理、更灵活;
  • Python 3.5 新引入了运算符 @,支持矩阵的点积(在 NumPy 库中有实现),特殊方法对应 __matmul__

13.5 众多比较运算符

  • 比较运算符和前面的中缀运算符的处理略有区别:
    • 正向和反向调用会使用数学意义的反向运算,无反向特殊方法;
    • ==!= 在反向调用失败后,会继续比较实例的 ID,以此为结果返回;
  • != 运算符的后备机制为 == 运算结果的取反,无需单独实现。

13.6 增量赋值运算符

  • 增量赋值不会修改不可变目标(不可变类型一定不能实现就地特殊方法),而是新建实例,并重新绑定变量;
  • 如果实现了就地运算符方法(__iadd__ 等),计算时会调用就地运算符方法,不创建新对象,必须返回自己 self
  • 不能盲目实现反向特殊方法:交换律可能不满足,需要考虑运算符本身逻辑。一般来说,如果正向方法只处理同类型操作数,则不需要实现反向方法,反向方法是为了处理类型不同的操作数
  • 小技巧:
    • 列表的 + 运算只能将两个列表加到一起,而 += 运算可以操作任何可迭代对象,其规则也适用于 .extend() 方法。+ 操作可接受的类型应当更严格
    • 导入标准库的语句要放在导入自己编写的模块之前;(与 C++不同)
    • 判断并利用对象的可迭代性时,可以用 iter(iterable) 创建迭代器,性能更佳。