流畅的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)
 in 
----> 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)
 in 
----> 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)
(, , )

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

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,Fluent Python 第十一章笔记)