19.3. abc — 抽象基础类 | 语言工具 |《python 3 标准库实例教程》| python 技术论坛-江南app体育官方入口
本节目标:定义和使用抽象基础类来进行接口认证。
为何要用抽象基础类?
抽象基础类是接口检查的一种形式,比起单独用 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 类管理功能识别出 subclassimplementation
是 pluginbase
抽象类实现的。
$ 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
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
类无法实例化,因为它只具备 value
和 constant
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
参阅
- -- 抽象基础类介绍
- -- collections 模块中有多种 collection 类型的抽象基础类。
- -- 数字的一种等级制度
- -- 战略模式(常见的插件实现模式)的介绍及例子。
- -- pycon 2013 介绍 -- doug hellmann
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 cc 协议,如果我们的工作有侵犯到您的权益,请及时联系江南app体育官方入口。