读书笔记:《流畅的Python》第13章 正确重载运算符

# 第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,也不知道为了啥? 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值