流畅的python,Fluent Python 第十三章笔记 (正确重载运算符)
13.1运算符重载基础
1、不能重载内置类型的运算符
2、不能新建运算符,只能重载现有的
3、某些运算符不能重载-----is、and、or、not(不过位运算符&、|和~可以)
13.2 一元运算符
- __neg__
+ __poes__
~ __invert__
一元操作符要符合一个原则,返回一个新对象,不能修改self。
from array import array import math import reprlib import numbers import functools from operator import xor class Vector: typecode = 'd' def __init__(self, components): self._components = array(self.typecode, components) def __iter__(self): # 返回一个迭代器,对象拥有__next__属性 '''有了__iter__属性,不仅可以多变量取值,还可以被for循环使用''' return iter(self._components) def __repr__(self): components = reprlib.repr(self._components) # 数量太多可以用...代替 # print(components) components = components[components.find('['): -1] return f'{self.__class__.__name__}({components})' def __str__(self): return str(tuple(self)) def __bytes__(self): return (bytes([ord(self.typecode)]) + bytes(self._components)) def __abs__(self): # abs返回一个直角三角形斜边长 return math.sqrt(sum(x * x for x in self)) def __neg__(self): # 返回一个负数 return Vector(-x for x in self) def __pos__(self): # 构建一个新的副本 return Vector(self) def __bool__(self): # 直接调用对象的abs值,然后用bool取值 return bool(abs(self)) def __getitem__(self, index): print('getitem') cls = type(self) if isinstance(index, slice): print(1) return cls(self._components[index]) elif isinstance(index, numbers.Integral): print('index', index) return self._components[index] else: msg = '{cls.__name__} indices must be integers' raise TypeError(msg.format(cls = cls)) shortcut_name = 'xyzt' # 定义在__getattr__里面也可以,定义在外面就可以修改了 def __getattr__(self, index): if len(index) == 1: pos = self.shortcut_name.find(index) if 0 <= pos < len(self._components): return self._components[pos] msg = '{.__name__!r} object has no attribute {!r}' raise AttributeError(msg.format(self.__class__, index)) def __setattr__(self, key, value): cls = type(self) # 取出类 if len(key) == 1: # 字符串不是一个字符都可以设置属性 if key in cls.shortcut_name: # 在定义的名单里面 error = 'readonly attribute {attr_name!r}' elif key.islower(): # 小写字符不行 error = "can't set attribute 'a' to 'z' in {cls_name!r}" else: # 另外的就是大写字符可以的 error = '' if error: msg = error.format(cls_name=cls, attr_name=key) raise AttributeError(msg) super(Vector, self).__setattr__(key, value) def __len__(self): return len(self._components) def __hash__(self): return functools.reduce(xor, (hash(i) for i in self._components), 0) def __eq__(self, other): return len(self) == len(other) and all(a == b for a, b in zip(self, other)) # 这个写的很漂亮,先判断len,在判断里面的每组元素,都用到了Python的短路原则 @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) # 先读取array的typecode menv = memoryview(octets[1:]).cast(typecode) print(menv) return cls(menv) v = Vector([1, 2]) print(Vector(v))
运行结果:
In [292]: v Out[292]: Vector([1.0, 2.0]) In [293]: -v Out[293]: Vector([-1.0, -2.0]) In [294]: +v Out[294]: Vector([1.0, 2.0]) In [295]: +v is v Out[295]: False
+返回的不是实例本身特例:Counter模块,添加+返回的是统计为大于0的数值
In [300]: from collections import Counter In [301]: dd = Counter('hello world') In [302]: dd Out[302]: Counter({'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}) In [303]: dd['r'] = -5 In [304]: dd Out[304]: Counter({'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': -5, 'd': 1}) In [305]: +dd Out[305]: Counter({'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'd': 1}) In [306]:
13.3重载向量加法运算符+
+ __add__
__radd__反向加法。
添加方法如下:
def __add__(self, other): # 两个数字相加遇到+号调用 pairs = zip_longest(self, other, fillvalue=0.0) return Vector(a + b for a, b in pairs)
In [310]: v1 = Vector([3,4]) In [311]: v+v1 Out[311]: Vector([4.0, 6.0]) In [312]: v + [1,2,3,4] Out[312]: Vector([2.0, 4.0, 3.0, 4.0])
通过zip_longest可以把缺省的位置用0.0替补。
当反着操作时候:
In [316]: range(3) + v1 --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-316-681cbdc03217> in <module> ----> 1 range(3) + v1 TypeError: unsupported operand type(s) for +: 'range' and 'Vector'
直接报错了。
再使用中缀运算符时,拿__add__来说,a+b好了,如果a对象有__add__方法,且返回的值不是NotImplemented,那就调用a.__add__(b),然后返回正确的结果。
如果a没有__add__方法,或者调用__add__返回的是NotImplemented,就会去检查b有没有__radd___方法,,如果有且不返回NotImplemented,那就调用b.__radd__(a),返回结果。
如果b没有__radd__方法或者__radd__方法返回NotImplemented,就抛出TypeError,并再错误消息中致命操作数类型不支持。
def __radd__(self, other): return self +other # 返回__add__的方法,可以理解为如果a+b中,a没有__add__或者返回NotImolemented,调用b+a
添加__radd__方法
In [318]: range(3) + v Out[318]: Vector([1.0, 3.0, 2.0]) In [319]: [1,2,3,4,5] + v Out[319]: Vector([2.0, 4.0, 3.0, 4.0, 5.0])
其实调用了__radd__后面操作了就是 v + range(3) 和 v+[1,2,3,4,5]
In [320]: v + 'abc' --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-320-aa871239e6fa> in <module> ----> 1 v + 'abc' <ipython-input-317-a17db2e69df8> in __add__(self, other) 199 def __add__(self, other): # 两个数字相加遇到+号调用 200 pairs = zip_longest(self, other, fillvalue=0.0) --> 201 return Vector(a + b for a, b in pairs) 202 203 def __radd__(self, other): <ipython-input-317-a17db2e69df8> in __init__(self, components) 120 121 def __init__(self, components): --> 122 self._components = array(self.typecode, components) 123 124 def __iter__(self): # 返回一个迭代器,对象拥有__next__属性 <ipython-input-317-a17db2e69df8> in <genexpr>(.0) 199 def __add__(self, other): # 两个数字相加遇到+号调用 200 pairs = zip_longest(self, other, fillvalue=0.0) --> 201 return Vector(a + b for a, b in pairs) 202 203 def __radd__(self, other): TypeError: unsupported operand type(s) for +: 'float' and 'str'
当我们执行v + 'abc'的时候,在return Vector(a + b for a, b in pairs)这个地方出错了,直接返回了TypeError没有去尝试调用对方的__radd__的方法,应该返回NotImplemented,去尝试调用对象的__radd__才是正确的。
def __add__(self, other): # 两个数字相加遇到+号调用 try: pairs = zip_longest(self, other, fillvalue=0.0) return Vector(a + b for a, b in pairs) except TypeError: return NotImplemented
对__add__进行了修改。
In [334]: 'abc' + v --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-334-866db194f057> in <module> ----> 1 'abc' + v <ipython-input-329-e69055f2613c> in __radd__(self, other) 97 98 def __radd__(self, other): ---> 99 return self +other # 返回__add__的方法,可以理解为如果a+b中,a没有__add__或者返回NotImolemented,调用b+a 100 101 @classmethod TypeError: unsupported operand type(s) for +: 'Vector' and 'str' In [335]: v + 'abc' --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-335-aa871239e6fa> in <module> ----> 1 v + 'abc' TypeError: unsupported operand type(s) for +: 'Vector' and 'str' In [336]:
我分别操作了两次,第一次'abc' + v:
先调用'abc'的__add__不行,返回调用v.__radd__,最后执行的还是v+'abc',报错。
v + 'abc':
先调用v.__add__不行,去寻找'abc'的__radd__,没有这个属性,直接报错。
两次执行的虽然最后都是执行v + 'abc',但由于过程不一样,中间的不错形式有点不同。
13.4重载标量乘法运算符。
def __mul__(self, other): return Vector(n * other for n in self) def __rmul__(self, other): return self * other
同样添加了*操作,本来可以跟前面一样通过try来实现__mul__的异常捕获,但书中准备用"白鹅类型",通过isinstance来检测other的属性。
def __mul__(self, other): if isinstance(other, numbers.Real): # numbers.Real包含的数字范畴很广了,包含了小数,分数等 return Vector(n * other for n in self) else: return NotImplemented # 不符合数字要求调用对方的__rmul__ def __rmul__(self, other): return self * other
上面是经过完全修改过的。
In [340]: v Out[340]: Vector([1.0, 2.0]) In [341]: 14 * v Out[341]: Vector([14.0, 28.0]) In [342]: v * 3.2 Out[342]: Vector([3.2, 6.4]) In [344]: v * False Out[344]: Vector([0.0, 0.0]) In [345]: from fractions import Fraction In [346]: v * Fraction(1,3) Out[346]: Vector([0.3333333333333333, 0.6666666666666666])
中缀运算符有很多
简单的加减乘除,地板除,取余。
+ __add__
- __sub__
* __mul__
/ __truediv__
// __floordiv__
% __mod__
反向操作符加一个r,就地操作符加一个i
等等还有不少。
13.5 众多比较运算符
分组 中缀运算符 正向方法调用 反向方法调用 后备机制
相等性 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 <b 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
当自己的调用,返回NotImplemented后,调用对象的反向方法,只有==与!=不会报错,因为==最后会对比双方的ID
在Python3中定义了__eq__,不需要重复定义__ne__
def __eq__(self, other): return len(self) == len(other) and all(a == b for a, b in zip(self, other))
执行结果:
In [348]: v Out[348]: Vector([1.0, 2.0]) In [349]: v == (1,2) Out[349]: True In [350]: v == [1,2] Out[350]: True In [351]: v == range(1,3) Out[351]: True In [352]: (1,2) == range(1,3) Out[352]: False In [353]:
从结果看出,定义这个不合适,这个判断不准确。
def __eq__(self, other): if isinstance(other, Vector): # 先进行判断是不是同一个类型的,不是同一个类型的调用对方的__eq__ return len(self) == len(other) and all(a == b for a, b in zip(self, other)) else: return NotImplemented
修改后的代码:
from array import array import math class Vector2d: typecode = 'd' __slots__ = ['__x', '__y', '__dict__'] def __init__(self, x, y): self.__x = x # 转换成私有变量 self.__y = y @property #把方法变成属性,而且是只读的 def x(self): return self.__x @property def y(self): return self.__y def __iter__(self): # 返回一个迭代器,对象拥有__next__属性 '''有了__iter__属性,不仅可以多变量取值,还可以被for循环使用''' return (i for i in (self.x, self.y)) def __repr__(self): class_name = type(self).__name__ return '{}({!r},{!r})'.format(class_name, *self) def __str__(self): return str(tuple(self)) def __bytes__(self): return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self))) def __eq__(self, other): return tuple(self) == tuple(other) def __abs__(self): # abs返回一个直角三角形斜边长 return math.hypot(self.x, self.y) def __bool__(self): # 直接调用对象的abs值,然后用bool取值 return bool(abs(self)) def __format__(self, format_spec=''): components = (format(c, format_spec) for c in self) # 使用生成器,用format处理属性 return '({},{})'.format(*components) # 解包后格式化输出 def __hash__(self): # 通过异或的方式,混合双方的哈希值。 return hash(self.x) ^ hash(self.y) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) # 先读取array的typecode menv = memoryview(octets[1:]).cast(typecode) print(menv) return cls(*menv) v = Vector2d(1, 2)
上一个老的2d版本的,到时候需要进行对比:
In [355]: v1 = Vector(range(1,3)) In [356]: v2 = Vector2d(1,2) In [358]: v1 Out[358]: Vector([1.0, 2.0]) In [359]: v2 Out[359]: Vector2d(1,2) In [360]: v1 == v2 Out[360]: True In [361]: v1 == range(1,3) Out[361]: False
这里最有意思的是,为什么v1等于v2,明显v2不属于Vector,所以return NotImplemented
这下要执行v2的__eq__
def __eq__(self, other): return tuple(self) == tuple(other)
执行这个就是执行Vector2d.__eq__(v2,v1),这个返回就是true了
假如执行对象的__eq__还是返回NotImplemented,就会执行返回id(v1) == id(v2),最后的大招。
所以==从来不会报错,不管你用什么对象比较。
13.6 增量赋值运算符。
增量赋值远算符 +=,-=,*=,/=,//=等等
增量赋值不会修改不可变目标。
所以不可变目标在用使用增量运算符:
比如 a += 3 就是a = a + 3
In [369]: id(v1) Out[369]: 4646331600 In [370]: v1 Out[370]: Vector([2.0, 4.0, 3.0]) In [371]: v1 *= 3 In [372]: v1 Out[372]: Vector([6.0, 12.0, 9.0]) In [373]: id(v1) Out[373]: 4650011152
对v1进行了*=操作,返现返回的是一个全新的对象,内部执行的是v1 = v1 *3
调用了v1的__mul__方法
书中对前期的一个binggo代码进行了重构。
# binggo import random from tombola import Tombila class BingoCage(Tombila): def __init__(self, item): self._randomizer = random.SystemRandom() self._items =[] self.load(item) # 调用load方法来实现初始化 def load(self, iterable): self._items.extend(iterable) self._randomizer.shuffle(self._items) def __add__(self, other): if isinstance(other, Tombila): # 返回两个对象的检查结果元祖相加,重新实例出一个对象 return BingoCage(self.inspect() + other.inspect()) else: return NotImplemented def __iadd__(self, other): if isinstance(other, Tombila): other_iterable = other.inspect() # 如果是Tombila的实例必定可以返回检测的元祖 else: try: other_iterable = iter(other) # 检测other是否为可迭代对象 except TypeError: self_cls = type(self).__name__ msg = f'right operand in += must be {self_cls!r} or an iterable' raise TypeError(msg) self.load(other_iterable) # 调用自身的load方法,load里面用extend return self def pick(self): try: return self._items.pop() except IndexError: # 没有数据可以弹出报错,接收IndexError,上报Look错误 raise LookupError('pick from empty BingoCage') def __call__(self, *args, **kwargs): # 对象变成可调用的 return self.pick() # 书中没有return,我自己加的,要不然执行对象没有返回值 if __name__ == '__main__': bingo = BingoCage('hello') print(bingo.inspect(), id(bingo)) bingo += 'abc' print(bingo.inspect(), id(bingo)) bingo1 = BingoCage('滚了') bingo = bingo + bingo1 print(bingo.inspect(), id(bingo))
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第十三章/bingo.py ('e', 'h', 'l', 'l', 'o') 4310112080 ('a', 'b', 'c', 'e', 'h', 'l', 'l', 'o') 4310112080 ('a', 'b', 'c', 'e', 'h', 'l', 'l', 'o', '了', '滚') 4311485328 Process finished with exit code 0
最后根据书中说明跟本人理解,中缀运算符,如果正向方法只能与自身同类实例进行操作
那就没必要设置反向方法。
因为你只能与自身同类的进行操作,如果对方跟你是同类,会调用你的方法,返回NotImplemented,调用对方的反向方法。
如果对方与你进行操作,对方不能执行自身的正向方法,说明对方肯定不跟你是同一类,既然不是同一类,你设置了反向方法又有什么意思呢?反向方法最后还不是调用自身的正向方法。