zoukankan      html  css  js  c++  java
  • 流畅的python,Fluent Python 第十章笔记

    序列的修改、散列和切片。

    书中讲了一些__getitem__还有__getattr__的一些使用等,前期我已经下了一些笔记,再次加强学习吧。

    from array import array
    import math
    import reprlib
    
    
    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 __eq__(self, other):
            return tuple(self) == tuple(other)
    
        def __abs__(self):    # abs返回一个直角三角形斜边长
            return math.sqrt(sum(x * x for x in self))
    
        def __bool__(self):    # 直接调用对象的abs值,然后用bool取值
            return bool(abs(self))
    
        @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])
    
    In [456]: v                                                                                        
    Out[456]: array('d', [1.0, 2.0])
    Vector([1.0, 2.0])
    
    In [457]: v = Vector(range(100))                                                                   
    
    In [458]: v                                                                                        
    Out[458]: array('d', [0.0, 1.0, 2.0, 3.0, 4.0, ...])
    Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
    
    In [459]: str(v)                                                                                   
    Out[459]: '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0)'
    
    In [468]: v.frombytes(bytes(v))                                                                    
    <memory at 0x108578940>
    Out[468]: array('d', [0.0, 1.0, 2.0, 3.0, 4.0, ...])
    Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
    
    In [469]:    
    

    10.3 协议和鸭子类型

    Python中创建完善的序列类型无需使用继承,只需实现符合序列协议的方法。

    在面向对象的编程中,协议是非正式的接口,只在文档中定义,在代码中不定义。

    列如,Python的序列协议只需要__len__和__getitem__的两个方法。

    任何类,只要使用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。

    10.4Vector类第二版:可切片的序列,切片原理。

        def __getitem__(self, index):
            return self._components[index]
    
        def __len__(self):
            return len(self._components)
    

     添加两个方法,实现序列协议。

    In [470]: v=Vector(range(10))                                                                      
    
    In [471]: v                                                                                        
    Out[471]: Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
    
    In [472]: v[3:5]                                                                                   
    Out[472]: array('d', [3.0, 4.0])
    
    In [473]: len(v)                                                                                   
    Out[473]: 10
    

    下面来了解切换的原理。

    In [475]: s= MySeq()                                                                               
    
    In [476]: s[1]                                                                                     
    Out[476]: 1
    
    In [477]: s[1:2]                                                                                   
    Out[477]: slice(1, 2, None)
    
    In [478]: s[1:5:2]                                                                                 
    Out[478]: slice(1, 5, 2)
    
    In [479]: s[1:2:2]                                                                                 
    Out[479]: slice(1, 2, 2)
    
    In [480]: s[1:1:2]                                                                                 
    Out[480]: slice(1, 1, 2)
    
    In [481]: s[1:2,3]                                                                                 
    Out[481]: (slice(1, 2, None), 3)
    
    In [482]: s[1:2,3:8]                                                                               
    Out[482]: (slice(1, 2, None), slice(3, 8, None))
    
    In [483]:   
    

     除了单个数字的时候,然会数字,另外的时候都返回了slice的实例。

    In [483]: dir(slice)                                                                               
    Out[483]: 
    ['__class__',
     '__delattr__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     'indices',
     'start',
     'step',
     'stop']
    

     'indices',帮我们优雅地处理确实索引和负数索引,已经长度超过目标缩影的切片

    In [484]: slice(None, 10, 2).indices(3)                                                            
    Out[484]: (0, 3, 2)
    
    In [485]: slice(-3,None).indices(10)                                                               
    Out[485]: (7, 10, 1)
    
    In [486]: slice(-3,None).indices(5)                                                                
    Out[486]: (2, 5, 1)
    
    In [1]: 'abcde'[:10:2]                                                                    
    Out[1]: 'ace'
    
    In [2]: 'abcde'[-3:]                                                                      
    Out[2]: 'cde'
    
    In [3]: 'abcde'[2:5:1]                                                                    
    Out[3]: 'cde'
    

     这么来看,我们很多使用切片时的参数没写,都是靠indices在默认工作,里面的参数应该是len(对象)的长度。

    10.4.2 能处理切片的__getitem__方法。

    前面通过切片返回的数组太low,现在先通过切片返回一个对象,单数字还是返回数值。

        def __getitem__(self, index):
            cls = type(self)
            if isinstance(index, slice):
                return cls(self._components[index])
            elif isinstance(index, numbers.Integral):
                return self._components[index]
            else:
                msg = '{cls.__name__} indices must be integers'
                raise TypeError(msg.format(cls = cls))
    
    In [496]: v = Vector(range(10))                                                                    
    
    In [497]: v[1:3]                                                                                   
    Out[497]: Vector([1.0, 2.0])
    
    In [498]: v[4]                                                                                     
    Out[498]: 4.0
    
    In [499]: v[4,5]                                                                                   
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-499-0f2464134463> in <module>
    ----> 1 v[4,5]
    
    <ipython-input-495-3d153cb2c4de> in __getitem__(self, index)
         45         else:
         46             msg = '{cls.__name__} indices must be integers'
    ---> 47             raise TypeError(msg.format(cls = cls))
         48 
         49     def __len__(self):
    
    TypeError: Vector indices must be integers
    

    10.5Vector类第3版:动态存取属性

    这里,老表又想通过v.x获取v[0],v.y获取v[1]等等

    老表厉害,

    Python对象获取属性

    首先通过__getattriburte__查寻自身是否拥有该属性,

    没有查找对象的类里面有没有这个属性,

    没有的话,按照继承树继续查找,

    妈的,还是找不到,就到了__getattr__里面来了。

        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))
    

     代码跟书中稍微有一点点不一样,装逼了一下,实例可以修改shortcut_name

    In [517]: v = Vector(range(3))                                                                     
    
    In [518]: v.x                                                                                      
    Out[518]: 0.0
    
    In [519]: v.y                                                                                      
    Out[519]: 1.0
    
    In [520]: v.t                                                                                      
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-520-ebe607c98c18> in <module>
    ----> 1 v.t
    
    <ipython-input-508-4a33821354a0> in __getattr__(self, index)
         54                 return self._components[pos]
         55             msg = '{.__name__!r} object has no attribute {!r}'
    ---> 56             raise AttributeError(msg.format(self.__class__, index))
         57 
         58     def __len__(self):
    
    AttributeError: 'Vector' object has no attribute 't'
    
    In [521]: v.shortcut_name='abc'                                                                    
    
    In [522]: v.b                                                                                      
    Out[522]: 1.0
    

     下面来给有意思的。

    In [521]: v.shortcut_name='abc'                                                                    
    
    In [522]: v.b                                                                                      
    Out[522]: 1.0
    
    In [523]: v.a                                                                                      
    Out[523]: 0.0
    
    In [524]: v.a = 10                                                                                 
    
    In [525]: v.a                                                                                      
    Out[525]: 10
    
    In [526]: v                                                                                        
    Out[526]: Vector([0.0, 1.0, 2.0])
    

     在里面修改了v.a的值,但实例里面确没有改。

    理解也比较简单,这里v.a只不过给v添加了一个属性而已,后续读取对象的v.a也不会调取___getattr__方法了。

    通过__setattr__可以限制行为,本来我还想为什么不修改对象的值,因为这个对象是只读的,所以只能限制通过单个字符赋值属性。避免误解

    还有就是如果实现了__getattr__方法,那么也要定义__setattr__方法,以防止对象的行为不一致。

    from array import array
    import math
    import reprlib
    import numbers
    
    
    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 __eq__(self, other):
            return tuple(self) == tuple(other)
    
        def __abs__(self):    # abs返回一个直角三角形斜边长
            return math.sqrt(sum(x * x for x in self))
    
        def __bool__(self):    # 直接调用对象的abs值,然后用bool取值
            return bool(abs(self))
    
        def __getitem__(self, index):
            cls = type(self)
            if isinstance(index, slice):
                return cls(self._components[index])
            elif isinstance(index, numbers.Integral):
                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)
        @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])
    

     这个测试不上了,测试过,可以运行。

    10.6 Ventor第4版:散列好快速等值测试

    前面两维的时候,通过两个数值的哈希值异或取得

    hash(self._x) ^ hash(self._y)

    这次要把多维数组里面的每个元素哈希后,一个接着一个异或。

    要用到reduce了。

    renduce(func,list)

    就是res = func(list[0],list[1])

    然后res = func(res,list[2])

    然后一直这么下去,知道元素取完,返回最后的返回值。

    In [535]: from functools import reduce                                                             
    
    In [536]: reduce(lambda a,b:a^b,range(10))                                                         
    Out[536]: 1
    
    In [537]: from operator import xor                                                                 
    
    In [538]: reduce(xor,range(10))                                                                    
    Out[538]: 1
    

     operator模块以函数的形式提供了python的全部中缀远算符,从而减少使用lambda表达式。(我不知道作者为什么这么不喜欢lambda函数)

    reduce最好在最后一个参数给一个初始值 initializer,因为如果reduce后面的可迭代对象为只有一个元素,会报错。

    所以在+、|、^设置初始值为0,(加,或,异或)

    在* 、&(乘号、于)运算初始值为1

    顺便把__eq__也修改了,因为两个list对比,如果是很长的元素,速度太慢了。

     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的短路原则

    测试了代码reduce里面用operator.xor,不填初始值也没问题。书中就是不填写的。

    zip函数在itertools里面有个zip_longest函数,还是蛮有意思的,上来测试下。

    In [5]: from itertools import zip_longest                                                 
    
    In [6]: list(zip_longest(range(1,4),'abc',[5,4,3,2,1],))                                  
    Out[6]: [(1, 'a', 5), (2, 'b', 4), (3, 'c', 3), (None, None, 2), (None, None, 1)]
    
    In [7]: list(zip_longest(range(1,4),'abc',[5,4,3,2,1],fillvalue=-1))                      
    Out[7]: [(1, 'a', 5), (2, 'b', 4), (3, 'c', 3), (-1, -1, 2), (-1, -1, 1)]
    
    In [8]: list(zip(range(1,4),'abc',[5,4,3,2,1],))                                          
    Out[8]: [(1, 'a', 5), (2, 'b', 4), (3, 'c', 3)]
    

     会按照最长的迭代对象打包,如果缺少,默认是None,也可用通过fillvalue填写默认参数。

    10.7format输出

    由于使用了一些数学公式,我数学忘记的太多了,就不上了。

  • 相关阅读:
    UITextField的总结
    【实战】登录界面
    点分治学习
    2020/3/1
    2020/2/29
    2020/2/28
    2020/2/27
    2020/2/27
    最小树形图
    Ch’s gift HDU6162
  • 原文地址:https://www.cnblogs.com/sidianok/p/12113537.html
Copyright © 2011-2022 走看看