• 流畅的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,调用对方的反向方法。

    如果对方与你进行操作,对方不能执行自身的正向方法,说明对方肯定不跟你是同一类,既然不是同一类,你设置了反向方法又有什么意思呢?反向方法最后还不是调用自身的正向方法。



  • 相关阅读:
    hihocoder [Offer收割]编程练习赛14 投掷硬币
    hihocoder [Offer收割]编程练习赛14 小Hi和小Ho的礼物
    CodeForces
    [HNOI2004] 打砖块
    CodeForces
    hdu4028 The time of a day[map优化dp]
    hdu5009 Paint Pearls[指针优化dp]
    hdu4719 Oh My Holy FFF[线段树优化dp]
    hdu1024 Max Sum Plus Plus[降维优化好题(貌似以后可以不用单调队列了)]
    hdu3709 Balanced Number[数位dp]
  • 原文地址:https://www.cnblogs.com/sidianok/p/12142964.html
Copyright © 2020-2023  润新知