19.3. abc — 抽象基础类

未匹配的标注

本节目标:定义和使用抽象基础类来进行接口认证。

为何要用抽象基础类?

抽象基础类是接口检查的一种形式,比起单独用 hasattr() 检查某些方法更加严格。通过定义抽象基础类,可以为子类建立一些通用的 API。抽象基础类可以让不熟悉源代码的人有能力快速熟悉并进一步去写插件扩展,而且可以在一个大型团队或大型项目组中需要保持对所有类的追踪有困难或根本不可能时提供一些帮助。

ABC(抽象基础类)是怎么工作的?

abc 具体的模式是把基础类中的方法抽象标记,之后注册具体的类作为抽象基础的实现。如果某应用程序或库需要某个特定的 API,可以用 issubclass() 或 isinstance() 来检查某对象的抽象类。

让我们开始吧,首先定义一个抽象基础类来表示一系列插件中保存和加载数据的 API。设置这个新的基础类的元类为 ABCMeta,然后用装饰器为这个类创建公共 API。之后的例子我们会用到 abc_base.py

abc_base.py

import abc

class PluginBase(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def load(self, input):
        """
                    从 input 中抽取数据并返回一个对象。
        """

    @abc.abstractmethod
    def save(self, output, data):
        """把 data 保存到 output 中。."""

注册具体的类

有两种方式来让某个具体的类实现抽象 API: 可以显式的注册类也可以直接从抽象基础上创建一个新的子类。使用类方法 register() 作为装饰器装饰到某个提供了所需 API 的具体的类上是显式的添加方式,这种就是不直接作为抽象基础类继承树上的一部分的添加方式。

abc_register.py

import abc
from abc_base import PluginBase

class LocalBaseClass:
    pass

@PluginBase.register
class RegisteredImplementation(LocalBaseClass):

    def load(self, input):
        return input.read()

    def save(self, output, data):
        return output.write(data)

if __name__ == '__main__':
    print('Subclass:', issubclass(RegisteredImplementation,
                                  PluginBase))
    print('Instance:', isinstance(RegisteredImplementation(),
                                  PluginBase))

例子中的 RegisteredImplementation 派生于 LocalBaseClass,但作为 PluginBase 的 API 实现被注册,所以 issubclass()isinstance() 也把它当做派生于 PluginBase 所对待了。

$ python3 abc_register.py

Subclass: True
Instance: True

通过子类实现抽象类

直接从基础类上派生子类可以避免显式的注册类。

abc_subclass.py

import abc
from abc_base import PluginBase

class SubclassImplementation(PluginBase):

    def load(self, input):
        return input.read()

    def save(self, output, data):
        return output.write(data)

if __name__ == '__main__':
    print('Subclass:', issubclass(SubclassImplementation,
                                  PluginBase))
    print('Instance:', isinstance(SubclassImplementation(),
                                  PluginBase))

上例中是依赖于正常的 Python 类管理功能识别出 SubclassImplementationPluginBase 抽象类实现的。

$ python3 abc_subclass.py

Subclass: True
Instance: True

使用子类的一个副作用是:在通过询问基础类寻找这样的插件实现时,可能会返回所有已知派生于此类的子类(这不是 abc 的功能,所有的类都有这个功能)。

abc_find_subclasses.py

import abc
from abc_base import PluginBase
import abc_subclass
import abc_register

for sc in PluginBase.__subclasses__():
    print(sc.__name__)

看,我们即使导入了 abc_register()RegisteredImplementation 也不会在子类列表中,因为它根本不是基础类的子类。

$ python3 abc_find_subclasses.py

SubclassImplementation

基类助手

忘记设置元类属性意味着实际的实现并不会让它们的 API 执行。为了方便设置基础类属性,有一个基础类具有自动设置元类的功能。

abc_abc_base.py

import abc

class PluginBase(abc.ABC):

    @abc.abstractmethod
    def load(self, input):
        """
                    从 input 中获取数据并返回一个对象。
        """

    @abc.abstractmethod
    def save(self, output, data):
        """将 data 保存到 output 中"""

class SubclassImplementation(PluginBase):

    def load(self, input):
        return input.read()

    def save(self, output, data):
        return output.write(data)

if __name__ == '__main__':
    print('Subclass:', issubclass(SubclassImplementation,
                                  PluginBase))
    print('Instance:', isinstance(SubclassImplementation(),
                                  PluginBase))

只需要继承 ABC 即可。

$ python3 abc_abc_base.py

Subclass: True
Instance: True

不完整的实现

直接使用继承于基础抽象类的子类有一个好处,那就是除非完整的实现了抽象类的 API 否则不会被实例化(也就不会有实例化时发生的错误)。

abc_incomplete.py

import abc
from abc_base import PluginBase

@PluginBase.register
class IncompleteImplementation(PluginBase):

    def save(self, output, data):
        return output.write(data)

if __name__ == '__main__':
    print('Subclass:', issubclass(IncompleteImplementation,
                                  PluginBase))
    print('Instance:', isinstance(IncompleteImplementation(),
                                  PluginBase))

上面的例子是一个不完整的实现,因为不是继承于基础抽象类的,这样在运行时就引发了一个不期望发生的错误。

$ python3 abc_incomplete.py

Subclass: True
Traceback (most recent call last):
  File "abc_incomplete.py", line 24, in <module>
    print('Instance:', isinstance(IncompleteImplementation(),
TypeError: Can't instantiate abstract class
IncompleteImplementation with abstract methods load

ABC 中的具体方法

尽管一个具体的类必须提供所有的抽象类方法的实现,抽象基础类还是可以提供了一些可以被 super() 调用的实现的。这些实现可以被用在实现基础类时重用某些寻常的逻辑,这样也可以强制让子类实现(潜在的)自己的逻辑。

abc_concrete_method.py

import abc
import io

class ABCWithConcreteImplementation(abc.ABC):

    @abc.abstractmethod
    def retrieve_values(self, input):
        print('base class reading data')
        return input.read()

class ConcreteOverride(ABCWithConcreteImplementation):

    def retrieve_values(self, input):
        base_data = super(ConcreteOverride,
                          self).retrieve_values(input)
        print('subclass sorting data')
        response = sorted(base_data.splitlines())
        return response

input = io.StringIO("""line one
line two
line three
""")

reader = ConcreteOverride()
print(reader.retrieve_values(input))
print()

由于 ABCWithConcreteImplementation() 是一个抽象基础类,也就没有直接实例化它使用的可能。子类 必须 提供 retrieve_values() 的覆盖实现,本例中的实现是在返回前将数据排下序。

$ python3 abc_concrete_method.py

base class reading data
subclass sorting data
['line one', 'line three', 'line two']

抽象属性

如果某 API 规范除了方法还有属性,那么可以将 abstractmethod()property() 结合来标出这个属性。

abc_abstractproperty.py

import abc

class Base(abc.ABC):

    @property
    @abc.abstractmethod
    def value(self):
        return 'Should never reach here'

    @property
    @abc.abstractmethod
    def constant(self):
        return 'Should never reach here'

class Implementation(Base):

    @property
    def value(self):
        return 'concrete property'

    constant = 'set by a class attribute'

try:
    b = Base()
    print('Base.value:', b.value)
except Exception as err:
    print('ERROR:', str(err))

i = Implementation()
print('Implementation.value   :', i.value)
print('Implementation.constant:', i.constant)

例子中的 Base 类无法实例化,因为它只具备 valueconstant getter 方法的抽象属性版本。value 属性虽然在 Implementation 里有一个具体的 property 实现,但 constant 却是以类属性定义的。

$ python3 abc_abstractproperty.py

ERROR: Can't instantiate abstract class Base with abstract
methods constant, value
Implementation.value   : concrete property
Implementation.constant: set by a class attribute

抽象可读可写的属性也是可以定义的。

abc_abstractproperty_rw.py

import abc

class Base(abc.ABC):

    @property
    @abc.abstractmethod
    def value(self):
        return 'Should never reach here'

    @value.setter
    @abc.abstractmethod
    def value(self, new_value):
        return

class PartialImplementation(Base):

    @property
    def value(self):
        return 'Read-only'

class Implementation(Base):

    _value = 'Default value'

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        self._value = new_value

try:
    b = Base()
    print('Base.value:', b.value)
except Exception as err:
    print('ERROR:', str(err))

p = PartialImplementation()
print('PartialImplementation.value:', p.value)

try:
    p.value = 'Alteration'
    print('PartialImplementation.value:', p.value)
except Exception as err:
    print('ERROR:', str(err))

i = Implementation()
print('Implementation.value:', i.value)

i.value = 'New value'
print('Changed value:', i.value)

具体实现的属性也必须与抽象类中一致,不管是可读可写还是只可读。PartialImplementation 中将可读可写的属性覆盖成一个只可读的 -- 也就是属性的 setter 方法没有被用到。

$ python3 abc_abstractproperty_rw.py

ERROR: Can't instantiate abstract class Base with abstract
methods value
PartialImplementation.value: Read-only
ERROR: can't set attribute
Implementation.value: Default value
Changed value: New value

使用装饰器语法的可读可写抽象属性,获取和设置的方法必须与同样的名字命名。

抽象类和静态方法

类和静态方法同样可被标记为抽象的。

abc_class_static.py

import abc

class Base(abc.ABC):

    @classmethod
    @abc.abstractmethod
    def factory(cls, *args):
        return cls()

    @staticmethod
    @abc.abstractmethod
    def const_behavior():
        return 'Should never reach here'

class Implementation(Base):

    def do_something(self):
        pass

    @classmethod
    def factory(cls, *args):
        obj = cls(*args)
        obj.do_something()
        return obj

    @staticmethod
    def const_behavior():
        return 'Static behavior differs'

try:
    o = Base.factory()
    print('Base.value:', o.const_behavior())
except Exception as err:
    print('ERROR:', str(err))

i = Implementation.factory()
print('Implementation.const_behavior :', i.const_behavior())

尽管类方法在类而不是实例上被调用,如果没定义过具体的实现的话仍然会被阻止实例化。

$ python3 abc_class_static.py

ERROR: Can't instantiate abstract class Base with abstract
methods const_behavior, factory
Implementation.const_behavior : Static behavior differs

参阅

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/pymotw/abc-abst...

译文地址:https://learnku.com/docs/pymotw/abc-abst...

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~