# 第13章 正确重载运算符
# 运算符重载的作用是让用户定义的对象使用中缀运算符或者一元运算符
"""
本章内容提要:
1 python如何处理中缀运算符中不同类型的操作数
2 使用鸭子类型或者显式类型检查处理不同类型的操作数
3 中缀运算符如何表明自己无法处理操作数的特殊行为
4 增量运算符(+=)的默认处理方式和重载方式
"""
# 运算符重载基础
"""
python对运算符重载施加的一些限制:
1.不能重载内置类型的运算符
2.不能新建运算符,只能重载现有的
3.某些运算符不能重载:
is and or not(不过位运算符&|~可以)
"""
# 13.2一元运算符
"""
一元运算符对应的方法
- : __neg__
+ :__pos__
~ : __invert__
对于整数,按位取反定义为:~x = -(x+1)
abs : __abs__
一元运算符的基本规则:始终返回一个新对象
多数时候+运算符最好返回self的副本
vector_v6.py
"""
# x和+x什么时候不相等?
# 示例13-2 算术运算上下文精度变了可能导致
"""
import decimal
ctx = decimal.getcontext() # 1获取当前全局算术运算的上下文引用
ctx.prec = 40 # 2精度设置为40
one_third = decimal.Decimal('1') / decimal.Decimal('3') # 3使用当前精度计算1/3
print(repr(one_third)) # 4查看结果,小数点后有40个数字
print(one_third == +one_third) # 5
# >>> True
ctx.prec = 28 # 6
print(one_third == +one_third) # 7
# >>>False
print(repr(one_third)) # 8小数点后有28个数字
# collections.Counter相加时,负值和0值会从结果中剔除,一元运算符+等同于加上一个空Counter
# 示例13-3 一元运算符+等同于加上一个空Counter,得到一个新的Counter实例,但是没有负值和0值
import collections
ct = collections.Counter('abracadabra')
print(repr(ct))
# >>>Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
ct['r'] = -3
ct['d'] = 0
print(repr(ct))
# >>>Counter({'a': 5, 'b': 2, 'c': 1, 'd': 0, 'r': -3})
print(repr(+ct))
# >>>Counter({'a': 5, 'b': 2, 'c': 1})
"""
# 13.3 重载向量加法运算符+
# vector_v6.py
"""
为了支持不同类型的运算,python为了中缀运算符提供了特殊的分派机制
对于表达式 a+b 解释器会执行以下几步操作:
1.如果a有__add__方法,而且返回值不是NotImplemented,调用a.__add__(b)方法,并返回结果
2.否则,检查b有没有__radd__方法,如果有而且不是返回NotImplemented,调用b.__radd__(a)
3.如果b没有__radd__或者返回NotImplemented,抛出错误,指明操作数类型不支持
"""
# 13.4 重载标量乘法运算符*
# vector_v6.py
# 13.5 众多的比较运算符
# vector_v6.py
"""
和算术运算符的区别:
1.正向和反向使用的是同一系列的方法,只是把参数对调了
"=="正向和反向都是调用__eq__
">="反向调用的是__le__
2.对于'=='和'!='如果反向调用失败,会比较对象的ID
"""
# 13.6 增量赋值运算符
# bingoaddable.py
"""
对于不可变类型来说,定义了__add__方法,不用定义__iadd__就能实现 a += b的操作
然而实现了就地运算符方法__iadd__,a += b的结果会就地修改a,而不会创建新的对象
"""
# 13.7 总结
"""本章首先介绍了python对运算符重载施加的一些限制
随后讲解了如何重载一元运算符
接着实现了中缀运算符
得出结论,一元运算符和中缀运算符的结果应该是新对象,绝对不能修改操作数
为了支持其他类型,我们返回特殊的NotImplemented(不是异常,而是一个单例值)
让解释器尝试对掉操作数,然后调用反向特殊方法
检测不能处理的操作数的两种方式:
鸭子类型,直接尝试执行运算,有问题,捕获TypeError
显式使用isinstance检查,要小心不能测试具体的类,而要测试抽象基类
接下来,讨探了比较运算符
最后讨论了增量赋值运算符"""
vector_v6.py
# vector_v6.py
# 实现一元运算符
from array import array
import math
import reprlib
import numbers
import functools
import operator
import itertools
class Vector:
typecode = 'd' # 类属性,在Vector实例和字节序列转化时使用
def __init__(self,components):
# _components是一个受保护的属性,把Vector的各分量保存在一个数组中
self._components = array(self.typecode,components)
def __iter__(self):
# 使用self._components构建一个迭代器
return iter(self._components)
def __repr__(self):
# 通过reprlib.repr获取self.__components的有限长度表示形式
components = reprlib.repr(self._components) # 超过5个使用...表示
# 去掉前缀部分
components = components[components.find('['):-1]
return 'Vetor({})'.format(components)
def __str__(self): # 显示为一个有序对
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+ # 把typecode转换成字节序列
bytes(self._components)) # 直接使用self.components创建bytes对象
# def __eq__(self, other): # 这样做会存在问题 如Vector2d(3,4) == [3,4]
# return tuple(self) == tuple(other)
# def __abs__(self):
# # 计算平方和然后开方
# return math.sqrt(sum(x for x in self))
def __bool__(self): # 把abs的结果转换为bool型
return bool(abs(self))
#从字节序列转换成Vector实例
@classmethod # 类方法装饰器
def frombytes(cls,octets):
typecode = chr(octets[0]) # 从第一个字节读取typecode
# 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv) # 不用拆包
def __len__(self):
return len(self._components)
def __getitem__(self, index):
cls = type(self)
if isinstance(index,slice):
# 如果是切片,调用类的构造方法使用_comonents的切片构建一个新的Vector对象
return cls(self._components[index])
elif isinstance(index,numbers.Integral):
# 如果是数字,返回数组_components的对应元素
return self._components[index]
else:
# 否则,抛出异常
msg = '{cls.__name__} indeices must be integers'
raise TypeError(msg.format(cls=cls))
"""
reprlib.repr方法用于生成大型结构或者递归结构的安全表示形式
它会限制输出字符串的长度,用...的形式表示
"""
shortcut_names = 'xyzt'
def __getattr__(self, name):
cls = type(self)
if len(name) == 1:
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self._components):
# 如果位置落在范围内,返回数组中对应的元素
return self._components[pos]
# 测试失败,抛出错误
msg = '{.__name__!r} object has no attribute{!r}'
raise AttributeError(msg.format(cls,name))
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
if name in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
error = "can't set attribute 'a' to 'z' in {cls_name!r}"
else:
error = ''
if error:
msg = error.format(cls_name=cls.__name__,attr_name=name)
raise AttributeError(msg)
super().__setattr__(name,value)
def __hash__(self):
hashes = (hash(x) for x in self._components)
# 上一行也可以写成这样
# hashes = map(hash,self._components)
# 参数0,表示初始值,能避免redunce()of empty sequence with no initial value异常
return functools.reduce(operator.xor,hashes,0)
# 改写__eq__方法,减少处理时间和内存用量
# def __eq__(self, other):
# if len(self) != len(other):
# return False
# # zip函数返回一个由元组构成的生成器
# for a,b in zip(self,other):
# if a != b :
# return False
# return True
# 使用all再次改良__eq__
# def __eq__(self, other):
# return len(self) == len(other) and all(a==b for a,b in zip(self,other))
"""==========================第五版新增部分=============================="""
def angel(self,n):
r = math.sqrt(sum(x*x for x in self[n:]))
a = math.atan2(r,self[n-1])
if (n == len(self) -1) and self[-1] < 0:
return math.pi * 2-a
else:
return a
def angels(self): # 创建生成器表达式,按需计算所有角坐标
return (self.angel(n) for n in range(1,len(self)))
def __format__(self, fmt_spec=''):
if fmt_spec.endswith('h'): # 超球面坐标
fmt_spec = fmt_spec[:-1]
coords = itertools.chain([abs(self)],self.angels())
outer_fmt = '<{}>'
else:
coords = self
outer_fmt = '({})'
componets = (format(c,fmt_spec) for c in coords)
return outer_fmt.format(','.join(componets))
"""==========================第6版新增部分=============================="""
def __abs__(self):
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 __add__(self, other):
# '''第一版'''
# pairs = itertools.zip_longest(self,other,fillvalue=0.0)
# return Vector(a+b for a,b in pairs)
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
# def __mul__(self, scalar):
# '''最简可用版本
# 计算向量的标量积'''
# return Vector(n * scalar for n in self)
def __mul__(self, scalar):
'''使用白鹅类型:显式检查抽象类型
使得__mul__支持不同的数据类型'''
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(map(lambda a,b:a*b,self,other))
except TypeError:
return NotImplemented
def __rmatmul__(self, other):
return self @ other
def __eq__(self, other):
'''之前的==会判别向量和一个元组或者任何可迭代对象相等,此版改进
先做类型检查,不是Vevtor的实例泽返回NotImplemented让python处理'''
if isinstance(other,Vector):
return len(self) == len(other) and \
all(a==b for a,b in zip(self,other))
else:
return NotImplemented
def __ne__(self, other):
'''继承的object的__ne__已经够用了,这个方法可以不实现'''
eq_result = self == other
if eq_result is NotImplemented:
return NotImplemented
else:
return not eq_result
if __name__ == '__main__':
# format测试
# 兼容Vector2d的方式
v1 = Vector([3,4])
print(format(v1))
print(format(v1, '.2f'))
print(format(v1, '.3e'))
# 多维向量测试
v3 = Vector([3,4,5])
print(format(v3))
print(format(Vector([1, 1]), 'h'))
print(format(Vector([1, 1,1]), 'h'))
print(format(Vector([2, 2,2]), '0.5fh'))
print('================================')
print(format(Vector([-1,-1,-1,-1]), 'h')) # ???
print('=======__add__方法也支持Vetor之外的对象==========')
v1 = Vector([3,4,5])
print(v1+(10,20,30))
# 但是对调操作数的位置以后第一版__add__无法处理
print((10, 20, 30)+v1)
print('=======__add__方法的操作数是要可迭代的==========')
# 这两个在pycharm中都没有报错
# print(v1 + 1)
# print(v1 + 'abc')
print('=======__mul__方法支持常规标量值和不那么寻常的数字类型==========')
v1 = Vector([1.0,2.0,3.0])
print(14 * v1)
print(v1 * True)
from fractions import Fraction
print(v1 * Fraction(1, 3))
print('=======__matmul__方法支持矩阵乘法==========')
va = Vector([1,2,3])
vb = Vector([5,6,7])
print(va @ vb)
print('=======新版__eq__方法==========')
vv = Vector([1,2,3])
t = (1,2,3)
print(vv == t)
bingoaddable.py
import abc
import random
class Tombola(abc.ABC): # 自己定义的抽象基类必须继承abc.ABC
@abc.abstractmethod # 抽象方法用@abc.abstractmethod装饰,而且定义体中通常只有文档字符串
def load(self,iterable):
'''从可迭代对象中添加元素'''
@abc.abstractmethod
def pick(self):
'''随机删除元素,然后将其返回
如果实例为空,这个方法应该抛出LookupError'''
def loaded(self): # 抽象基类可以包含具体方法
'''如果至少有一个元素,返回True
否则返回False
抽象基类只能依赖抽象基类定义的接口如inspect'''
return bool(self.inspect())
def inspect(self):
'''返回一个有序元组,有当前元素构成
我们不知道具体子类如何存储元素,不过为了得到inspect的结果
可以不断pick把Tombola清空,然后调用
.load把所有元素放回去'''
items = []
while 1:
try:
items.append(self.pick())
except LookupError:
break
self.load(items)
return tuple(sorted(items))
# 11.7.2定义抽象基类的子类
# 定义BingoCage类
class BingoCage(Tombola):
def __init__(self,items):
'''假设我们将在线上游戏中使用这个,random.SystemRandom
使用os.urandom()函数实现random API
生成适合用于加密的字节序列'''
self._randomizer = random.SystemRandom()
self._items = []
self.load(items) # 委托load实现初始加载
def loaded(self,items):
self._items.extend(items)
# 没有使用random.shuffle而是使用self._randomize的shuflle方法
self._randomizer.shuffle(self._items)
def pick(self): # 重写了pick方法
try:
return self._items.pop()
except IndexError:
raise LookupError('pick from empty BingoCage')
def __call__(self):
return self.pick()
"""以上复制11章的BingoCage类"""
import itertools
class AddableBingoCage(BingoCage):
def __add__(self, other):
if isinstance(other,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)
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)
return self
算术运算符和比较运算符对应的方法
运算符 正向方法 反向方法 后备机制 == a.__eq__(b) b.__eq__(a) 返回id(a)==id(b) != a.__ne__(b) b.__ne__(a) 返回not (a == b) > a.__gt__(b) b.__lt__(a) 返回TypeError >= a.__ge__(b) b.__le__(a) 返回TypeError < a.__lt__(b) b.__gt__(a) 返回TypeError <= a.__le__(b) b.__ge__(a) 返回TypeError
运算符 正向方法 反向方法 就地方法 说明 + __add__ __radd__ __iadd__ - __sub__ __rsub__ __isub__ * __mul__ __rmul__ __imul__ / __truediv__ __rtruediv__ __itruediv__ // __floordiv__ __rfloordiv__ __ifloordiv__ ** __pow__ __rpow__ __ipow__ % __mod__ __rmod__ __imod__ divmod() __divmod__ __rdivmod__ __idivmod__ @ __matmul__ __rmatmul__ __imatmul__ 矩阵乘法 | __or__ __ror__ __ior__ & __and__ __rand__ __iand__ ^ __xor__ __rxor__ __ixor__ 异或 >> __rshift__ __rrshift__ __irshift__ << __lshift__ __rlshift__ __ilshift__
35岁学Python,也不知道为了啥?