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

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

 



posted @ 2020-01-03 01:30  就是想学习  阅读(353)  评论(0编辑  收藏  举报