python中的abc module
为什么使用abstract base classes?
抽象基类是一种比hasattr()检查特定方法更严格的接口检查形式。通过定义抽象基类,就可以为一组字类建立公共API。
How ABCs Work?
abc将基础类方法标记为抽象,然后将具体类注册为抽象类的实现。
# abc_base.py
import abc
class PluginBase(metaclass=abc.ABCMeta):
@abc.abstractmethod
def load(self, input):
"""Retrieve data from the input source
and return an object.
"""
@abc.abstractmethod
def save(self, output, data):
"""Save the data object to the output."""
注册一个具体的类
# 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))
$ python3 abc_register.py
Subclass: True
Instance: True
从这个案例中,我们可以清楚看到RegisteredImplementation 继承自LocalBaseClass.但是issubclass() 与 isinstance()将他们看作继承自PluginBase.
Implementation through Subclassing
# 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))
$ python3 abc_subclass.py
Subclass: True
Instance: True
SubClassing有一个副作用,通过向基类询问派生自plug-in的已知类的列表,可以找到plug-in的所有实现.
# 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不在子类列表中,因为它实际上不是从基类派生的。
Helper Base Class
# abc_abc_base.py
import abc
class PluginBase(abc.ABC):
@abc.abstractmethod
def load(self, input):
"""Retrieve data from the input source
and return an object.
"""
@abc.abstractmethod
def save(self, output, data):
"""Save the data object to the 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))
$ python3 abc_abc_base.py
Subclass: True
Instance: True
不完整实现
类似于Go中接口,当一个struct 实现所有的接口方法,才可以实现接口。
# 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
ABCs中的具体方法
# 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()
Since ABCWithConcreteImplementation() is an abstract base class, it is not possible to instantiate it to use it directly. Subclasses must provide an override for retrieve_values(), and in this case the concrete class sorts the data before returning it.
$ python3 abc_concrete_method.py
base class reading data
subclass sorting data
['line one', 'line three', 'line two']
抽象属性
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)
$ 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
The Base class in the example cannot be instantiated because it has only an abstract version of the property getter methods for value and constant. The value property is given a concrete getter in Implementation and constant is defined using 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)
The concrete property must be defined the same way as the abstract property, as either read-write or read-only. Overriding a read-write property in PartialImplementation with one that is read-only leaves the property read-only – the property’s setter method from the base class is not reused.
$ 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