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

    接口:从协议到抽象基类。

    这一章,不知道是翻译问题,还是我能力问题,看的不是很懂,只能简单记录一下自己的理解。一段时间以后再回看了。

    首先协议是非正式的接口,每个类(除了抽象基类),都有接口。

    受保护的属性与私有属性不在接口中。

    接口是对象公开方法的子集,让对象在系统中扮演特定的角色

    接口是实现特定角色的方法集合,协议与继承没有关系,一个类可能实现多个接口,从而扮演对个角色。

    序列协议是Python最基础的协议之一。

    In [29]: from collections.abc import Sequence                                                      
    
    In [30]: class Foo: 
        ...:     def __getitem__(self, pos): 
        ...:         return range(0, 30, 10)[pos] 
        ...:                                                                                           
    
    In [31]: f = Foo()                                                                                 
    
    In [32]: f[1]                                                                                      
    Out[32]: 10
    
    In [33]: for i in f:print(i)                                                                       
    0
    10
    20
    
    In [34]: 20 in f                                                                                   
    Out[34]: True
    
    In [35]: isinstance(f, Sequence)                                                                   
    Out[35]: False
    
    In [36]:  
    

     上面定义了__getitem__实现了序列协议的一部分,很明显,它不是Sequence的子类,尽管也没有__iter__与__Container__但还是能被for循环调用,以及能使用in

    In [35]: isinstance(f, Sequence)                                                                   
    Out[35]: False
    
    In [36]: from collections import Iterable                                                          
    /usr/local/bin/ipython:1: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
      #!/usr/local/opt/python/bin/python3.7
    
    In [37]: isinstance(f, Iterable)                                                                   
    Out[37]: False
    

     记得以前老师说过,能被for循环的调用的都是可迭代对象,很明显,f能被for循环调用,但不是可迭代对象,严谨的还是具有__iter__的才是可迭代对象。

    11.3使用猴子布丁实现协议。

    猴子补丁以前了解过一下,也就知道这个名字,现在书中很详细的介绍了,也实际使用了。

    就是已经实例对象一开始没有这个属性,通过对它实例的类添加属性,然后它就有了这个方法,一般要少用。

    In [46]: import collections 
        ...:  
        ...: Card = collections.namedtuple('Card', 'rank suit') 
        ...:  
        ...: class Frenchdeck: 
        ...:     ranks = [str(n) for n in range(2, 11)] + list('JQKA')   # 把牌的数字与花色赋值给类属性
        ...:  
        ...:     suits = 'spades diamonds clubs hearts'.split() 
        ...:  
        ...:     def __init__(self):               # 用列表生成式制作一副牌 
        ...:         self._cards = [Card(rank, suit) for rank in self.ranks 
        ...:                       for suit in self.suits] 
        ...:  
        ...:     def __len__(self): 
        ...:         return len(self._cards) 
        ...:  
        ...:     def __getitem__(self, item):   # 定义这个[]取值会用到。 
        ...:         return self._cards[item] 
        ...:  
        ...:     def __repr__(self): 
        ...:         return f'{self._cards!r}' 
        ...:                                                                                           
    
    In [47]: desk = Frenchdeck()                                                                       
    
    In [48]: desk[:5]                                                                                  
    Out[48]: 
    [Card(rank='2', suit='spades'),
     Card(rank='2', suit='diamonds'),
     Card(rank='2', suit='clubs'),
     Card(rank='2', suit='hearts'),
     Card(rank='3', suit='spades')]
    
    In [49]: shuffle(desk)                                                                             
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-49-1b00429a849a> in <module>
    ----> 1 shuffle(desk)
    
    /usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/random.py in shuffle(self, x, random)
        276                 # pick an element in x[:i+1] with which to exchange x[i]
        277                 j = randbelow(i+1)
    --> 278                 x[i], x[j] = x[j], x[i]
        279         else:
        280             _int = int
    
    TypeError: 'Frenchdeck' object does not support item assignment
    
    In [50]: def set_card(deck, position, card): 
        ...:     deck._cards[position] = card 
        ...:      
        ...:                                                                                           
    
    In [51]: Frenchdeck.__setitem__ = set_card                                                         
    
    In [52]: shuffle(desk)                                                                             
    
    In [53]: desk[:5]                                                                                  
    Out[53]: 
    [Card(rank='Q', suit='clubs'),
     Card(rank='4', suit='diamonds'),
     Card(rank='10', suit='hearts'),
     Card(rank='9', suit='spades'),
     Card(rank='5', suit='diamonds')]
    

     通过对第一章的扑克牌添加猴子布丁,前面由于缺少__setitem__属性,所以shuffle无法对齐进行使用,通过后面添加__setitem__对象实现了部分可变序列的协议既可。

    所谓的鸭子类型就是无需关注对象的具体类型,只要实现了特定的协议既可,不需要继承。

    11.5定义抽象基类的子类

    In [54]: from collections.abc import MutableSequence                                               
    
    In [55]: class Mu(MutableSequence): 
        ...:     ... 
        ...:                                                                                           
    
    In [56]: mu = Mu()                                                                                 
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-56-c234f3d4f57f> in <module>
    ----> 1 mu = Mu()
    
    TypeError: Can't instantiate abstract class Mu with abstract methods __delitem__, __getitem__, __len__, __setitem__, insert
    

     collections.abc里面有16个抽象基类,这里我继承了MutableSequence,从报错可以看出__delitem__, __getitem__, __len__, __setitem__, insert,属于抽象方法,必须子类重新定义。

    书中按照FranchDeck继承MutalbeSquence

    import collections.abc
    
    Card = collections.namedtuple('Card', 'rank suit')
    
    
    class Frenchdeck2(collections.abc.MutableSequence):
        ranks = [str(n) for n in range(2, 11)] + list('JQKA')  # 把牌的数字与花色赋值给类属性
        suits = 'spades diamonds clubs hearts'.split()
    
        def __init__(self):  # 用列表生成式制作一副牌
            self._cards = [Card(rank, suit) for rank in self.ranks
                           for suit in self.suits]
    
        def __len__(self):  # 继承类的抽象方法
            return len(self._cards)
    
        def __getitem__(self, item):  # 定义这个[]取值会用到。
            return self._cards[item]  # 继承类的抽象方法
    
        def __delitem__(self, key):  # 继承类的抽象方法
            del self._cards[key]
    
        def __setitem__(self, key, value):  # 继承类的抽象方法
            self._cards[key] = value
    
        def insert(self, index:int, value) -> None:  # 继承类的抽象方法
            self._cards.insert(index, value)
    
        def __repr__(self):
            return f'{self._cards!r}'
    
    
    if __name__ == '__main__':
        deck = Frenchdeck2()
        deck.insert(0, 1)
    

     把所有的抽象方法都定义了,不管你用不用到。

    同时你也进程了抽象类MutableSequence中的一些方法,比如remove,pop,extend,__iadd__

    In [78]: deck[0]                                                                                   
    Out[78]: 1
    
    In [79]: deck.pop()                                                                                
    Out[79]: Card(rank='A', suit='hearts')
    
    In [80]: deck.pop()                                                                                
    Out[80]: Card(rank='A', suit='clubs')
    
    In [81]: deck.extend('123')                                                                        
    
    In [82]: deck.remove('2')   
    

    collections.abc16个基类里面我比较又兴趣的是Callable与Hashable是来可以测试对象是否可以调用与哈希。

    In [83]: from collections.abc import Callable, Hashable                                            
    
    In [84]: isinstance(print, Callable)                                                               
    Out[84]: True
    
    In [85]: isinstance(print, Hashable)                                                               
    Out[85]: True
    
    In [86]: isinstance(list, Hashable)                                                                
    Out[86]: True
    
    In [87]: isinstance(list(), Hashable)                                                              
    Out[87]: False
    
    In [88]: callable(list)                                                                            
    Out[88]: True
    
    In [89]:  
    

    Callable抽象基类可以用callable函数一样的效果,Hashable只能用这个抽象类测试了。

    11.6.2 抽象基类的数字塔

    numbers的包

    官方解释链接:

    https://docs.python.org/zh-cn/3.7/library/numbers.html#numbers.Rational.numerator

    Number

    Complex

    Real

    Rational   (是Intergal的子类)

    Integral

    In [103]: from numbers import Integral                                                             
    
    In [104]: isinstance(1,Integral)                                                                   
    Out[104]: True
    
    In [105]: from fractions import Fraction                                                           
    
    In [106]: f = Fraction(3,4)                                                                        
    
    In [107]: isinstance(f,Integral)                                                                   
    Out[107]: False
    
    In [108]: from numbers import Rational                                                             
    
    In [109]: isinstance(f,Integral)                                                                   
    Out[109]: False
    
    In [110]: isinstance(f,Rational)                                                                   
    Out[110]: True
    
    In [111]:  
    

     Integral的实例包括int,bool(int的子类)

    Rational多了包含分数

    11.7定义并使用一个抽象基类。

    为了更好的学习抽象基类,书中定义了一个抽象基类,并继承了两个子类,一个虚拟子列。

    import abc
    
    class Tombila(abc.ABC):
        
        @abc.abstractmethod
        def load(self, iterable):
            """从可迭代对象添加元素"""
            
        @abc.abstractmethod
        def pick(self):
            print(type(self).__name__ + 'pick is working')
            """随机删除元素并返回
            如果实例为空,这个方法抛出"LookupError"
            """
        
        def loaded(self):
            """如果至少有一个元素,返回True,否则返回False"""
            return bool(self.inspect())
    
        def inspect(self):
            """返回一个有序的元祖, 由当前元素构成""" 
            items = []
            while True:
                try:
                    items.append(self.pick())
                except LookupError:
                    break
            self.load(items)              # 返回读取
            return tuple(sorted(items))
    

     上面的是抽象类,我在pick里面定义了一些功能,继承的时候还是可以通过super调用。

    11.7.1抽象基类的语法解释。

    Python3.4之后可以这样,就上面写的直接继承abc.ABC就可以了。

    3.4之前

    class Demo(metaclass=abc.ABCMeta):
        ...
    

     Python2这样写:

    class Demo(object):
        __metaclass__ = abc.ABCMeta
    

    在Python3.3之前还有:

    @abc.abstractclassmethod
        @abc.abstractproperty
        @abc.abstractstaticmethod
    

     新在都不用了,根据多层装饰器的原理,你只要把@abstractmethod放在最里层,上面再写上你需要装饰的,同样可以继承给子类。

    11.7.2 定义Tombola抽象基类的子类。

    先上抽线基类:

    import abc
    
    class Tombila(abc.ABC):
    
        @abc.abstractmethod
        def load(self, iterable):
            """从可迭代对象添加元素"""
    
        @abc.abstractmethod
        def pick(self):
            print(type(self).__name__ + 'pick is working')
            """随机删除元素并返回
            如果实例为空,这个方法抛出"LookupError"
            """
    
        def loaded(self):
            """如果至少有一个元素,返回True,否则返回False"""
            return bool(self.inspect())
    
        def inspect(self):
            """返回一个有序的元祖, 由当前元素构成"""
            items = []
            while True:
                try:
                    items.append(self.pick())
                except LookupError:
                    break
            self.load(items)              # 返回读取
            return tuple(sorted(items))
    
    if __name__ == '__main__':
        print(dir(Tombila))
    

    下面分别用了三种继承的方式来实现这个功能。

    第一种改动最少,两个方法都是继承了基类,第二个改懂比较多,把基类的方法都改了,第三个直接是虚拟子类,不继承基类的任何方法。

    # 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 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(range(10))
        print(bingo())
        print(bingo.inspect())
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第十一章/bingo.py
    6
    (0, 1, 2, 3, 4, 5, 7, 8, 9)
    
    Process finished with exit code 0
    

     第二种继承:

    import random
    
    from tombola import Tombila
    
    class LotteryBlower(Tombila):
    
        def __init__(self, iterable):
            self._balls = list(iterable)
    
        def load(self, iterable):
            self._balls.extend(iterable)
    
        def pick(self):
            try:
                position = random.randrange(len(self._balls))   # 内部随机选一个数字
                return self._balls.pop(position)
            except ValueError:     # 如果len里面为0,ValueError: empty range for randrange()
                raise LookupError('pick from empty LotteryBlower') # 抓取前面的错误返回需要的错误
    
        def loaded(self):
            return bool(self._balls)
    
        def inspect(self):
            return tuple(sorted(self._balls))
    
    
    if __name__ == '__main__':
        lottery = LotteryBlower(range(10))
        print(lottery.pick())
        print(lottery.inspect())
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第十一章/lotto.py
    4
    (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    
    Process finished with exit code 0
    

    11.7.3 Tombola的虚拟子类

    虚拟子类不会继承注册的抽象基类,换句话说,直接点,就是虚拟子类跟抽象基类其实一点关系都没有。

    你可以不继承或者修改抽象基类的抽象方法,但为了避免运行错误,没有继承么,所以要把基类的所有方法重写一遍。

    先上一个极端的不继承任何基类的例子。

    from random import randrange
    
    from tombola import Tombila
    
    
    
    class A:
        ...
    
    Tombila.register(A)
    
    
    
    a = A()
    print(isinstance(a, Tombila))
    print(issubclass(A, Tombila))
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第十一章/tombolist.py
    True
    True
    
    Process finished with exit code 0
    

     可以看到代码,虚拟子类的效果。

    这是书中的代码:

    from random import randrange
    
    from tombola import Tombila
    
    
    
    @Tombila.register
    class TomboList(list):
    
        def pick(self):        # self就是列表本身了
            if self:
                position = randrange(len(self))
                return self.pop(position)
            else:
                raise LookupError('pop from empty Tombolist')
    
        load = list.extend         # load 直接等于list.extend方法
    
        def loader(self):
            return bool(self)
    
        def inspect(self):
            return tuple(sorted(self))
    
    # Tombila.register(TomboList)  这是Python3.3之前的写法
    
    if __name__ == '__main__':
        tombo = TomboList(range(10))
        print(tombo.pick())
        print(tombo.inspect())
        print(tombo.load(range(3)))    # 等同与tombo.extend(range(3))
        print(tombo.inspect())
        print(TomboList.__mro__)
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第十一章/tombolist.py
    1
    (0, 2, 3, 4, 5, 6, 7, 8, 9)
    None
    (0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9)
    (<class '__main__.TomboList'>, <class 'list'>, <class 'object'>)
    
    Process finished with exit code 0
    

     可以看出来,再__mro__里面没有看到基类Tombila。

    我后来想了一下,如果基类与list方法没有重名(本来也没有),可以双继承。

    from random import randrange
    
    from tombola import Tombila
    
    
    
    
    class TomboList(Tombila,list):
    
        def pick(self):        # self就是列表本身了
            if self:
                position = randrange(len(self))
                return self.pop(position)
            else:
                raise LookupError('pop from empty Tombolist')
    
        load = list.extend         # load 直接等于list.extend方法
    
        def loader(self):
            return bool(self)
    
        def inspect(self):
            return tuple(sorted(self))
    
    # Tombila.register(TomboList)  这是Python3.3之前的写法
    
    if __name__ == '__main__':
        tombo = TomboList(range(10))
        print(tombo.pick())
        print(tombo.inspect())
        print(tombo.load(range(3)))    # 等同与tombo.extend(range(3))
        print(tombo.inspect())
        print(TomboList.__mro__)
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第十一章/tombolist_1.py
    9
    (0, 1, 2, 3, 4, 5, 6, 7, 8)
    None
    (0, 0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8)
    (<class '__main__.TomboList'>, <class 'tombola.Tombila'>, <class 'abc.ABC'>, <class 'list'>, <class 'object'>)
    
    Process finished with exit code 0
    

     这样也不是为一种方法,但虚拟基类后面书中介绍了,Python中的一些用法。

    11.8 Tombola子类的测试方法。

    测试用的doctest模块,我没怎么用过,所以也不写了,包括代码中有用到查寻虚拟子类。

    Tombila._abc_registry
    但我的运行失败,报错没有这个属性。

    11.9 Python使用register的方式。

    Python会把tuple,str,range,memoryview注册为虚拟子类。

    Squence.regiser(tuple)

    ....

    包括字典这种,也会注册给MutableMapple

    MutableMapple.register(dict)

    11.10 鹅的行为由可能像鸭子

    class Sruggle:
        def __len__(self):
            return 23
    
    
    
    print(issubclass(Sruggle, Sized))
    print(Sized.__subclasscheck__(Sruggle))
    

     返回的都是true

    这个Sruggle不是SIzed的虚拟子类,更不可能是子类。

    但因为Sized有__subclasscheck__特殊的类方法。

    下面我按照上面的代码抄写的Sized的原码:

    import abc
    
    class Sized(abc.ABC):
    
        __slots__ = ()
    
        @abc.abstractmethod
        def __len__(self):
            return 0
    
        @classmethod
        def __subclasshook__(cls, C):
            if cls is Sized:
                if any("__len__" in B.__dict__ for B in C._mro__):
                    return True
            return NotImplemented
    

     书中没有介绍issubclass运行的原理,我认为如果有__subclasshook__应该会优先调用该类方法。

    但一般__subclasshook__用很少,自己用的机会更加少。

















    
    
  • 相关阅读:
    Python制作天气查询软件【python实战必学】
    Python妹子图爬虫实战项目【新手必学】
    Python超级无敌技巧分享
    PlaySound使用进阶
    【】2019
    【】风之忧伤
    delete[]和delete
    《windows程序设计 第五版》实例
    playsound函数用法
    【Python入门自学笔记专辑】——Python跳转语句和循环使用范围
  • 原文地址:https://www.cnblogs.com/sidianok/p/12129223.html
Copyright © 2011-2022 走看看