• guxh的python笔记七:抽象基类


    1,鸭子类型和白鹅类型

    1.1,白鹅类型

    白鹅类型对接口有明确定义,比如不可变序列(Sequence),需要实现__contains__,__iter__,__len__,__getitem__,__reversed__,index,count。

    对于其中的抽象方法,子类在继承时必须具体化,其余非抽象方法在继承时可以自动获得,Sequence序列必须具体化的抽象方法是__len__和__getitem__

    from collections import abc
    
    class Foo(abc.Sequence):
    
        def __init__(self, components):
            self._components = components
    
        def __getitem__(self, item):
            return self._components[item]
    
        def __len__(self):
            return len(self._components)
    

    Foo通过继承abc.Sequence,明确了自己的身份,明确了自己需要实现哪些方法,并且会自动获得哪些方法, 这样的正式继承即为白鹅类型。

    Foo的实例就是正式的Sequence类了,可以通过isinstance检查,可以使用自己创建的抽象方法,可以使用继承抽象基类获得的方法:

    f = Foo(list('abcde'))
    print(isinstance(f, abc.Sequence))   # 结果True
    print(f[0])   # 'a',__getitem__
    print(len(f))   # 5,__len__
    print('b' in f)   # True,__contains __
    for i in f:   # __iter__
      print(i) 
    print(list(reversed(f)))  # ['e', 'd', 'c', 'b', 'a'], __reversed__
    print(f.count('a'))  # 1, count
    print(f.index('a'))  # 0, index
    

    1.2,鸭子类型

    鸭子类型没有明确的接口,只是遵循了一定的协议,比如python序列协议只需要实现__len__和__getitem__方法,对于序列,这点鸭子类型和白鹅类型中Sequence抽象基类的要求相同,但白鹅类型Sequence继承后能够自动获得抽象基类的方法,而鸭子类型不会有这些方法:

    class Foo:
    
        def __init__(self, components):
            self._components = components
    
        def __getitem__(self, item):
            return self._components[item]
    
        def __len__(self):
            return len(self._components)
    

    Foo自己实现的2个方法可以用:

    print(f[0])   # 'a', __getitem__
    print(len(f))   # 5, __len__

    Foo自己没有实现的3个方法不可用:

    print(list(reversed(f)))  # error
    print(f.count('a'))  # error
    print(f.index('a'))  # error
    

    下面2个自己没实现的方法也可以用,为什么呢?原因是python发现某些特殊方法没有实现时,会自动尝试调用其他特殊方法:

    print('b' in f)   # 可以用,python的in测试会依次尝试调用__contains__,__iter__,__getitem__
    for i in f:   # 可以用,python的迭代会依次尝试调用__iter__,__getitem__
      print(f)
    

    另外鸭子类型的Foo,无法通过isinstance检查:

    print(isinstance(f, abc.Sequence))   # 结果False
    

    2,抽象基类

    抽象基类的主要作用:1)作为超类定义接口;2)isinstance检查;3)注册:不子类化就声明实现接口;4)自动识别:不注册/不子类化就让抽象基类能够识别虚拟子类。

    猴子补丁:运行时修改类,不改变源码,FrenchDeck.__setitem__ = set_card,例如给FrenchDeck类补上set_card()方法。

    2.1,自己定义抽象基类 - abc

    自定义抽象基类需要继承abc.ABC(不继承无法约束子类必须实现抽象方法),并且用@abc.abstractmethod装饰抽象方法,抽象基类无法实例化。

    子类在基础抽象基类时,必须将抽象方法实现,否则无法实例化。

    import abc
    
    class Tombola(abc.ABC):     # 继承abc.ABC
    	
      @abc.abstractmethod       # 声明抽象方法
      def load(self):
        """这里是注释"""
    		
      def inspect(self):      # 非抽象方法
        pass
    

    2.2,内置抽象基类 - collections.abc

    Iterable:__iter__

    Container:__contains__

    Sized:__len__

    Sequence:不可变序列

    Mapping:不可变映射

    Set:不可变集合

    MappingView

    Callable、Hashable

    Iterator、Iterable

    可以通过对抽象基类实例化,来查看强制要求实现的抽象方法:

    >>> import collections
    >>> collections.Sequence()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: Can't instantiate abstract class Sequence with abstract methods __getitem__, __len__
    

    2.3,内置抽象基类 - numbers

    Number:数基类

    Complex:复数 = 实数 + 虚数

    Real:实数 = 有理数 + 无理数

    Rational:有理数 = 整数 + 分数

    Intergral:整数

    例如:判断浮点数可以用isinstance(obj, numbers.Real)

    如果obj是bool,int,float,fractions.Fraction,或外部库(例如numpy)提供的非复数类型,都能返回True

    用type(obj, float)则比较局限

    2.4,内置抽象基类 — io

    定义了IO处理相关的抽象基类

    2.5,注册 — 让基类拥有虚拟子类

    通过注册,可以不用继承,就建立基类和“虚拟子类”之间的关系。有二个关键点:

    1)注册后,python会相信我们,直接让“虚拟子类”通过isinstance检查,也不会约束虚拟子类必须实现抽象方法。

    2)“虚拟子类”不会从抽象基类获得任何方法和属性。

    综上所述:为避免运行时出错,注册后我们应该手动让“虚拟子类”实现“基类”的所有方法。

    注册方法一:装饰器(python3.3之后支持)

    @Tombola.register   # 装饰器注册
    class TomboList(list):   def load(self):     pass

    注册方法二:标准调用语法

    class TomboList(list):  
      def load(self):  
        pass
    
    Tombola.register(TomboList)   # 标准调用语法注册

    注册后,虚拟子类的实例化对象,就能够通过isinstance检查:

    t = TomboList()
    isintance(t, Tombola)   # True

     对象注册后,如果缺少某些方法尚未实现,会报错提示

    2.6,自动识别

    抽象基类有个方法__subclasshook__,通过判断其他类是否具有一些特殊方法(失败返回NotImplement让虚拟子类继续判断),来识别其他类是不是自己的“虚拟子类”,例如:

    class Struggle:
      def __len__(self):
        pass
    s = Struggle()
    isintance(s, abc.Sized)  # True
    

    必须具有__subclasshook__的抽象基类才能提供该功能。

    ps:自己建了个类,实现了collections.Sequence所有方法,但是无法通过collections.Sequence检查,collections.Sequence貌似没有实现该方法。

  • 相关阅读:
    界面开发注意要素,降低界面BUG
    软件测试及其重要性
    浅谈自动化测试工具之Selenium
    浅谈探索式软件测试
    浅谈功能测试流程
    关于软件测试中测试用例的重要性
    第1章 性能测试整体认知
    数据库对比,优化
    2、补充介绍
    1、MarkDown基本语法
  • 原文地址:https://www.cnblogs.com/guxh/p/10206630.html
Copyright © 2020-2023  润新知