当前位置: 首页 > 工具软件 > ABC > 使用案例 >

Python中的ABC(抽象基类)

万俟皓
2023-12-01

毕设老师给的代码里有一行__metaclass__=ABCmeta
搜了搜网上关于ABC的资料,大多是英文链接,就自己Google翻译了一下。

abc–Abstract Base Classes(抽象基类)
目的:在代码中定义和使用抽象基类进行API检查。

为何使用抽象基类?
抽象基类是接口检查的一种形式,对比特定方法的单独hasattr()检查更加严格。此功能在第三方将提供实现的情况下(例如与应用程序的插件一起使用)特别有用,同时当你在大型团队工作时或当你的代码量很大有可能很难记住所有类的时候,也可以给你提供帮助。

ABCs如何工作
abc的工作方式是将基类的方法标记为抽象,然后将具体类注册为抽象基的实现。如果你的代码需要特定的API,则可以使用issubclass()或isinstance()对照抽象类检查对象。
首先,我们定义一个抽象基类来表示一组用于保存和加载数据的插件的API。

import abc
class PluginBase(object):
      __metaclass__=abc.ABCMeta
      
      @abc.abstractmethod
      def load(self,input):
          """从输入源检索数据并返回一个对象"""
          return
      @abc.abstractmethod
      def save(self,output,data):
          """将数据对象保存到输出。"""
          return
          

注册一个具体类
有两种指示具体类实现抽象的方法:在abc或直接从abc的子类中注册该类。

import abc
from abc_base import PluginBase
class RegisteredImplementation(object):
      def load(self,input):
          return input.read()
      def save(self,output,data):
          return output.write(data)
PluginBase.register(RegisteredImplementation)

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

在此示例中,PluginImplementation不是派生自PluginBase,而是注册为实现PluginBase API
运行结果:

Subclass:True
Instance:True

通过子类实现
通过直接从基类子类化,我们可以避免显式注册类的需要。

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类管理用于将PluginImplementation识别为实现抽象的PluginBase
运行结果:

Subclass:True
Instance:True

使用直接子类化的副作用是,可以通过向基类询问从其派生的已知类的列表来找到插件的所有实现(这不是abc功能,所有类都可以做到这一点)。

import abc
from abc_base import PluginBase
import abc_subclass
import abc_register
for sc in PluginBase.__subclasses__():
    print sc.__name__

注意,即使已导入abc_register,RegisteredImplementation也不在子类列表中,因为它实际上并不是从基类派生的。
AndréRoberge博士描述了使用此功能来发现插件的方法,即动态地将所有模块导入目录中,然后查看子类列表以查找实现类。
不完全实现
直接从抽象基类进行子类化的另一个好处是,除非完全实现API的抽象部分,否则无法实例化该子类。 这样可以防止半完成的实现在运行时触发意外错误。

import abc
from abc_base import PluginBase
class IncompleteImplementation(PluginBase):
      def save(self,output,data):
          return output.write(data)
PluginBase.register(IncompleteImplementation)
if __name__=='__main__':
    print 'Subclass:',issubclass(IncompleteImplementation,PluginBase)
    print 'Instance:',isinstance(IncompleteImplementation(),PluginBase)
Subclass: True
Instance:
Traceback (most recent call last):
  File "abc_incomplete.py", line 22, in <module>
    print 'Instance:', isinstance(IncompleteImplementation(), PluginBase)
TypeError: Can't instantiate abstract class IncompleteImplementation with abstract methods load

ABCs中的实例化方法
尽管具体类必须提供抽象方法的实现,但是抽象基类也可以提供通过super()调用的实现。 这使你可以通过将通用逻辑放在基类中来重用通用逻辑,但是可以强制子类提供具有(可能)自定义逻辑的重写方法。

import abc
from cStringIO import StringIO
class ABCWithConcreteImplementation(object):
     __metaclass__=abc.ABCMeta
     @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=StringIO("""line one
line two
line three
""")
reader=ConcreteOverride()
print reader.retrieve_values(input)
print 

由于ABCWithConcreteImplementation是抽象基类,因此无法实例化它以直接使用它。 子类必须提供retrieve_values()的重写,在这种情况下,具体类会在返回数据之前先对数据进行梳理。
结果如下:

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

抽象属性
如果您的API规范除了方法之外还包括属性,则可以通过使用@abstractproperty定义它们来在具体类中要求这些属性。

import abc
class Base(object):
     __metaclass__=abc.ABCMeta
     
     @abc.abstractproperty
     def value(self):
         return 'Should never get here'
class Implementation(Base):
    @property
    def value(self):
        return 'concrete property'
try:
    b=Base()
    print 'Base.value:',b.value
except Exception,err:
    print 'ERROR:',str(err)
i=Implementation()
print 'Implementation.value:',i.value

该示例中的基类无法实例化,因为它只有属性getter方法的抽象版本。

ERROR: Can't instantiate abstract class Base with abstract methods value
Implementation.value: concrete property

您还可以定义抽象的读/写属性。

import abc
class Base(object):
    __metaclass__=abc.ABCMeta
    def value_getter(self):
       return 'Should never see this'
    def value_setter(self,newvalue):
       return
    value=abc.abstractproperty(value_getter,value_setter)
class PartialImplementation(Base):
    @abc.abstractproperty
    def value(self):
        return 'Read-only'
class Implementation(Base):
    _value='Default value'
    def value_getter(self):
        return self._value
    def value_setter(self,newvalue):
        self._value=newvalue
    value=property(value_getter,value_setter)
try:
    b=Base()
    print 'Base.value',b.value
except Exception,err:
    print 'ERROR:',str(err)
try:
   p=PartialImplementation()
   print 'PartialImplementation.value:',p.value
except Exception,err:
   print'ERROR:',str(err)
i=Implementation()
print 'Implementation.value:',i.value
i.value='New value'
print 'Changed value:',i.value

注意,必须以与抽象属性相同的方式定义具体属性。 试图用只读属性覆盖PartialImplementation中的读/写属性不起作用。

ERROR: Can't instantiate abstract class Base with abstract methods value
ERROR: Can't instantiate abstract class PartialImplementation with abstract methods value
Implementation.value: Default value
Changed value: New value
``
要使用装饰器语法处理读/写抽象属性,获取和设置值的方法应命名为相同.

```python
import abc
class Base(object):
    __metaclass__=abc.ABCMeta
    @abc.abstractproperty
    def value(self):
        return 'should never see this'
    @value.setter
    def value(self,newvalue):
       return
class Implementation(Base):
    _value='Default value'
    @property
    def value(self):
       return self._value
    @value.setter
    def value(self,newvalue):
       self._value=newvalue
i=Implementation()
print 'Implementation.value:',i.value
i.value='New value'
print 'Changed value:',i.value

请注意,尽管Base和Implementation类中的两个方法都具有不同的signature(不会翻译),但它们都被命名为value()。

Implementation.value: Default value
Changed value: New value

渣渣翻译

 类似资料: