《Fluent Python》 CH.13_面向对象_正确重载运算符 (可以重载加减乘除、取反取正、位运算等,部分支持不new对象的就地重载)
小结
-
P551-P589,共计 38页
-
有些事情让我不安,比如运算符重载。我决定不支持运算符重载, 这完全是个人选择,因为我见过太多 C++ 程序员滥用它。
——James Gosling Java 之父
主要内容:
-
Python 如何处理中缀运算符中不同类型的操作数
-
使用鸭子类型或显式类型检查处理不同类型的操作数
-
中缀运算符如何表明自己无法处理操作数
-
众多比较运算符(如 ==、>、<=,等等)的特殊行为
-
增量赋值运算符(如 +=)的默认处理方式和重载方式
-
本章速读
1. Java不支持运算符重载,因为Java之父不要这么搞
2. 支持重载取负数/取正数/取反的重载,分别重写对象的__neg__、__pos__和__invert__即可.
3. 处理add和radd(“右向”)的冲突,同时出现的话,先执行左对象的add再执行右对象的radd。
4. 重载乘法: __mul__ 和 __rmul__
5. 就地加法:就地运算符方法,例如 __iadd__。
13.1 运算符重载基础
- 不能重载内置类型的运算符
- 不能新建运算符,只能重载现有的
- 某些运算符不能重载——is、and、or 和 not(不过位运算符 &、| 和 ~ 可以)
13.2 一元运算符
-(neg) 一元取负算术运算符。如果 x 是 -2,那么 -x == 2。
+(pos) 一元取正算术运算符。通常,x == +x,但也有一些例外。如果好 奇,请阅读“x 和 +x 何时不相等”附注栏。
~(invert) 对整数按位取反,定义为 ~x == -(x+1)。如果 x 是 2,那么 ~x == -3;如果是对象,在重载后需要返回对应的反集等相似意思。
支持一元运算符很简单,只需实现相应的特殊方法。这些特殊方法只有 一个参数,self。然后,使用符合所在类的逻辑实现。不过,要遵守运 算符的一个基本规则:始终返回一个新对象。也就是说,不能修改 self,要创建并返回合适类型的新实例。
13.3 重载向量加法运算符+
重载.__add__ 方法
处理add和radd(“右向”)的冲突
为了支持涉及不同类型的运算,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,并在错误消息中指明操作数类型 不支持
13.4 重载标量乘法运算符*
Vector([1, 2, 3]) * x 是什么意思?
如果 x 是数字,就是计算标量 积(scalar product),结果是一个新 Vector 实例,各个分量都会乘以 x——这也叫元素级乘法(elementwise multiplication),使用np.dot()来计算点乘即可。
v1 = list([1, 2, 3])
print(v1 * 10)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
回到标量积的话题。我们依然先实现最简可用的 mul 和 rmul 方法:
# 在Vector类中定义
def __mul__(self, scalar):
return Vector(n * scalar for n in self)
def __rmul__(self, scalar):
return self * scalar
13.5 众多比较运算符
对于 __ne__,现在 Python 3 返回结果是对 __eq__ 结果的取反。
13.6 增量赋值运算符
如果定义了 add 方法的话,不用编写额 外的代码,+= 就能使用。
语法糖而已.
如果实现了就地运算符方法,例如 iadd,计算 a += b 的 结果时会调用就地运算符方法。这种运算符的名称表明,它们会就地修 改左操作数,而不会创建新对象作为结果。