zoukankan      html  css  js  c++  java
  • 《Fluent Python》CH.11_面向对象_接口:从协议到抽象基类

    主要内容

    • 首先,本章说明抽象基类的常见用途:实现接口时作为超类使用
    • 说明抽象基类如何检查具体子类是否符合接口定义,以及如何使 用注册机制声明一个类实现了某个接口,而不进行子类化操作
    • 最后, 说明如何让抽象基类自动“识别”任何符合接口的类——不进行子类化或注册。

    补充知识点

    第十章&第十一章:

    • 协议,指来自父类的继承关系、或者接口的强制性约束要求
    • 鸭子类型:看起来像只鸭子,吃起来是鸭肉,无论它是否是鸭蛋孵化出来的,那么它就是鸭子。在示例10-3中,FrenchDeck没有继承序列父类,但是还是自己实现了len和getitem方法,可以像序列类(继承 abc.Sequence)一样使用,就够了。
      • 我们发现 Python 对序列协议的支持十分深入。如果一个 类实现了 getitem 方法,此外什么也没做,那么 Python 会设法迭 代它,而且 in 运算符也随之可以使用。
    • “白鹅类型”,可以使用抽象基类明确声明接 口,而且类可以子类化抽象基类或使用抽象基类注册(无需在继承关系 中确立静态的强链接),宣称它实现了某个接口。

    其他

    • 转换命令: jupyter nbconvert --to markdown E:PycharmProjectsTianChiProject0_山枫叶纷飞competitions13_fluent_pythonCH.11_面向对象_接口:从协议到抽象基类.ipynb

    11.1 Python文化中的接口和协议

    • Python 语言没有 interface 关键字,,而且除了抽象基类,每个类都算是接口
    • 受保护的属性和私有属性不在接口中:即便“受保护的”属性 也只是采用命名约定实现的(单个前导下划线);私有属性可以轻松地 访问(参见 9.7 节),原因也是如此。不要违背这些约定。

    11.2 Python喜欢序列

    Python数据模型的哲学是尽量支持基本协议。对序列来说,即便是最简 单的实现,Python 也会力求做到最好。

    图 11-1 展示了定义为抽象基类的 Sequence 正式接口。
    ?.png
    图 11-1:Sequence 抽象基类和 collections.abc 中相关抽象类的 UML 类图,箭头由子类指向超类,以斜体显示的是抽象方法

    示例 11-3 定义 getitem 方法,只实现序列协议的一部分, 这样足够访问元素、迭代和使用 in 运算符了:

    
    
    from typing import overload, _T, Iterable
    class Foo:
        def __getitem__(self, pos):
            return range(0, 30, 10)[pos]
    
    f = Foo()
    f[1]
    
    10
    
    for i in f:
        print(i)
    
    
    0
    10
    20
    
    20 in f
    
    True
    
    print('测试此外,使用 isinstance 和 issubclass 测试抽象基类更为人接受。=> 是否适用于鸭子类型')
    import collections
    print(isinstance(f, collections.MutableSequence))
    print(issubclass(Foo, collections.MutableSequence))
    
    测试此外,使用 isinstance 和 issubclass 测试抽象基类更为人接受。=> 是否适用于鸭子类型
    False
    False
    

    显然,上面的是鸭子类型

    虽然没有 iter 方法,但是 Foo 实例是可迭代的对象,因为发现有 getitem 方法时,Python 会调用它,传入从 0 开始的整数索引, 尝试迭代对象(这是一种后备机制)。

    尽管没有实现 contains 方 法,但是 Python 足够智能,能迭代 Foo 实例,因此也能使用 in 运算 符:Python 会做全面检查,看看有没有指定的元素。

    综上,鉴于序列协议的重要性,如果没有 itercontains 方法,Python 会调用 getitem 方法,设法让迭代和 in 运算符可 用。

    11.3 使用猴子补丁在运行时实现协议

    如下, (代码与示例 1-1 相 同)

    示例 11-4 实现序列协议的 FrenchDeck 类

    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 suit in self.suits for rank in self.ranks]
        def __len__(self):
            return len(self._cards)
        def __getitem__(self, position):
            return self._cards[position]
    
    

    示例 11-5 random.shuffle 函数不能打乱 FrenchDeck 实例

    from random import  shuffle
    deck = FrenchDeck()
    shuffle(deck)
    
    ---------------------------------------------------------------------------
    
    TypeError                                 Traceback (most recent call last)
    
    <ipython-input-7-2d8d99c4f0db> in <module>
          1 from random import  shuffle
          2 deck = FrenchDeck()
    ----> 3 shuffle(deck)
          4 
    
    
    D:DevInstallProjectsMiniconda3lib
    andom.py in shuffle(self, x, random)
        305                 # pick an element in x[:i+1] with which to exchange x[i]
        306                 j = randbelow(i+1)
    --> 307                 x[i], x[j] = x[j], x[i]
        308         else:
        309             _int = int
    
    
    TypeError: 'FrenchDeck' object does not support item assignment
    

    错误消息相当明确,“'FrenchDeck' object does not support item assignment”('FrenchDeck' 对象不支持为元素赋值)。

    这个问题的原因是,shuffle 函数要调换集合中元素的位置,而 FrenchDeck 只实现 了不可变的序列协议。

    可变的序列还必须提供 setitem 方法。

    Python 是动态语言,因此我们可以在运行时修正这个问题,甚至还可以 在交互式控制台中。

    示例 11-6 为FrenchDeck 打猴子补丁,把它变成可变的,让 random.shuffle 函数能处理

    def set_card(deck, position, card):
        deck._cards[position] = card
    
    
    FrenchDeck.__setitem__ = set_card
    shuffle(deck)
    
    deck[0:5]
    
    
    
    [Card(rank='5', suit='clubs'),
     Card(rank='2', suit='diamonds'),
     Card(rank='6', suit='spades'),
     Card(rank='8', suit='diamonds'),
     Card(rank='K', suit='hearts')]
    

    11.4 Alex Martelli的水禽

    讲了几个故事,略

    要点

    • 使用 isinstance 和 issubclass 测试抽象基类

    11.5 定义抽象基类的子类

    在示例 11-8 中,我们明确把 FrenchDeck2 声明为 collections.MutableSequence 的子类:

    • 为了支持洗牌,只需实现 setitem 方法。
    • 是继承 MutableSequence 的类必须实现 delitem 方法,这是MutableSequence 类的一个抽象方法
    • 此外,还要实现 insert 方法,这是 MutableSequence 类的第三个抽象方法
    import collections
    Card = collections.namedtuple('Card', ['rank', 'suit'])
    class FrenchDeck(collections.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 suit in self.suits for rank in self.ranks]
        def __len__(self):
            return len(self._cards)
    
        def __getitem__(self, position):
            return self._cards[position]
    
        def insert(self, position: int, value: object)-> None:
            self._cards.insert(position, value)
    
        def __setitem__(self, position: int, value: object) -> None:
            self._cards[position] = value
    
        # def __delitem__(self, position: int)-> None:
        #     del self._cards[position]
    
    f = FrenchDeck()
    
    
    ---------------------------------------------------------------------------
    
    TypeError                                 Traceback (most recent call last)
    
    <ipython-input-19-63de8e231a57> in <module>
    ----> 1 f = FrenchDeck()
          2 
          3 
    
    
    TypeError: Can't instantiate abstract class FrenchDeck with abstract methods __delitem__
    

    导入时(加载并编译 frenchdeck2.py 模块时),Python 不会检查抽象方 法的实现,在运行时实例化 FrenchDeck2 类时才会真正检查。因此, 如果没有正确实现某个抽象方法,Python 会抛出 TypeError 异常,如上。

    FrenchDeck继承的抽象基类collections.MutableSequence的抽象方法有:

    @abstractmethod
    def __setitem__(self, index, value):
        raise IndexError
    
    @abstractmethod
    def __delitem__(self, index):
        raise IndexError
    
    @abstractmethod
    def insert(self, index, value):
        'S.insert(index, value) -- insert value before index'
        raise IndexError
    

    11.6.1 collections.abc模块中的抽象基类

    Iterable、Container 和 Sized

    • 各个集合应该继承这三个抽象基类,或者至少实现兼容的协 议。Iterable 通过 iter 方法支持迭代,Container 通过 contains 方法支持 in 运算符,Sized 通过 len 方法支持 len() 函数。

    Sequence、Mapping 和 Set

    • 这三个是主要的不可变集合类型,而且各自都有可变的子类;如可变序列MutableSequence、可变Set(MutableSet)等

    MappingView

    • MappingView 在 Python 3 中,映射方法 .items()、.keys() 和 .values() 返回 的对象分别是 ItemsView、KeysView 和 ValuesView 的实例。前两个 类还从 Set 类继承了丰富的接口。

    Callable 和 Hashable

    • 这两个抽象基类与集合没有太大的关系,只不过因为 collections.abc 是标准库中定义抽象基类的第一个模块,而它们又 太重要了,因此才把它们放到 collections.abc 模块中。
    • 我(指作者)从未见过 Callable 或 Hashable 的子类。
    • 这两个抽象基类的主要作用是为内置 函数 isinstance 提供支持,以一种安全的方式判断对象能不能调用或 散列。

    Iterator

    • 注意它是 Iterable 的子类。

    11.6.2 抽象基类的数字塔

    numbers包定义的是“数 字塔”(即各个抽象基类的层次结构是线性的),其中 Number 是位于 最顶端的超类,随后是 Complex 子类,依次往下,最底端是 Integral 类:

    • Number
      • 如果想检查一个数是不是整数,可以使用 isinstance(x, numbers.Integral),这样代码就能接受 int、bool(int 的子 类),或者外部库使用 numbers 抽象基类注册的其他类型。
    • Complex
    • Real
      • 与之类似,如果一个值可能是浮点数类型,可以使用 isinstance(x, numbers.Real) 检查。这样代码就能接受 bool、int、float、fractions.Fraction,或者外部库(如 NumPy,它做了相应的注册)提供的非复数类型。
    • Rational
    • Integral

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

    使用@abc.abstractmethod注解,可以强列要求该类的这个抽象方法derived from it。

    import abc
    class Tombola(abc.ABC):
        @abc.abstractmethod # 强列要求该类的方法derived from it
        def load(self, iterable):
            """从可迭代对象中添加元素。"""
    
    

    11.7.1 抽象基类句法详解

    声明抽象基类最简单的方式是继承 abc.ABC(Python 3.4 新增的类) 或其他抽象基类。
    低于Python 3.4的版本,需要在 class 语句中使 用 metaclass= 关键字,把值设为 abc.ABCMeta(不是 abc.ABC):

    class Tombola(metaclass=abc.ABCMeta):
        # ...
    

    metaclass= 关键字参数是 Python 3 引入的。
    在 Python 2 中必须使用 metaclass 类属性:

    class Tombola(object): # 这是Python 2!!!
        __metaclass__ = abc.ABCMeta
        # ...
    

    11.7.3 Tombola的虚拟子类

    注册虚拟子类的方式是在抽象基类上调用 register 方法。这么做之 后,注册的类会变成抽象基类的虚拟子类,而且 issubclass 和 isinstance 等函数都能识别,但是注册的类不会从抽象基类中继承任 何方法或属性。

    虚拟子类不会继承注册的抽象基类,而且任何时候都不会检 查它是否符合抽象基类的接口,即便在实例化时也不会检查。为了 避免运行时错误,虚拟子类要实现所需的全部方法。

    print('实现一个简单的虚拟子类 -- ')
    import abc
    class Father(abc.ABC):
        @abc.abstractmethod # 强列要求该类的方法derived from it
        def load(self, iterable):
            """从可迭代对象中添加元素。"""
    
    @Father.register
    class Son():
        pass
    
    实现一个简单的虚拟子类 -- 
    

    虚拟子类需要注意的地方

    如果是 Python 3.3 或之前的版本,不能把 .register 当作类装饰器 使用,必须使用标准的调用句法。

    注册之后,可以使用 issubclass 和 isinstance 函数判断 TomboList 是不是 Tombola 的子类:

    issubclass(Son, Father)
    
    
    True
    
    son = Son()
    isinstance(son, Father)
    
    
    True
    
    print('重新一次运行时错误: ')
    son.load()
    
    
    重新一次运行时错误: 
    
    
    
    ---------------------------------------------------------------------------
    
    AttributeError                            Traceback (most recent call last)
    
    <ipython-input-30-1ad84f9b17cd> in <module>
          1 print('重新一次运行时错误: ')
    ----> 2 son.load()
          3 
          4 
    
    
    AttributeError: 'Son' object has no attribute 'load'
    

    类的继承关系在一个特殊的类属性中指定: __mro__

    然而,类的继承关系在一个特殊的类属性中指定—— __mro__,即方法 解析顺序(Method Resolution Order)。

    Son.__mro__
    
    (__main__.Son, object)
    
    Father.__mro__
    
    
    (__main__.Father, abc.ABC, object)
    
    Father.__subclasses__()
    
    
    
    
    []
    

    11.9 Python使用register的方式

    虽然现在(python3.4及以后)可以把 register 当作装饰器使用了,但更常见的做法还是把 它当作函数使用,用于注册其他地方定义的类。
    。例如,在 collections.abc 模块的源码中 (https://hg.python.org/cpython/file/3.4/Lib/_collections_abc.py),是这样 把内置类型 tuple、str、range 和 memoryview 注册为 Sequence 的 虚拟子类的:

    Sequence.register(tuple)
    Sequence.register(str)
    Sequence.register(range)
    Sequence.register(memoryview)
    

    其他几个内置类型在 _collections_abc.py 文件 (https://hg.python.org/cpython/file/3.4/Lib/_collections_abc.py)中注册为 抽象基类的虚拟子类。

    这些类型在导入模块时注册,这样做是可以的, 因为必须导入才能使用抽象基类:能访问 MutableMapping 才能编写 isinstance(my_dict, MutableMapping)。

    无法理解!!!!

    
    
    你不逼自己一把,你永远都不知道自己有多优秀!只有经历了一些事,你才会懂得好好珍惜眼前的时光!
  • 相关阅读:
    SQL 通配符
    C#+winform登陆界面案例
    C#+winform登陆界面案例
    c# implicit explicit关键字(隐式和显式数据类型转换)
    c# implicit explicit关键字(隐式和显式数据类型转换)
    解决:SqlDateTime 溢出。必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间提示问题
    解决:SqlDateTime 溢出。必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间提示问题
    C#里面的三种定时计时器:Timer
    C#里面的三种定时计时器:Timer
    C# FileSystemWatcher用法详解
  • 原文地址:https://www.cnblogs.com/zhazhaacmer/p/14412726.html
Copyright © 2011-2022 走看看