abc模块

abc: 是 Python 标准库中的一个模块,主要用于定义抽象基类(Abstract Base Classes)。抽象基类提供了一种机制,允许我们在面向对象编程中定义接口,以确保子类实现特定的方法或属性。
示例代码:

from abc import ABC, abstractmethod

class ABCBase(ABC):
    @abstractmethod
    def hello(self):
        pass

    def hi(self):
        pass

class MyClass(ABCBase):
    def fun(self):
        print('this is fun')

上述代码运行不报错。但是当对MyClass进行实例化时,就会提示错误。
myclass = MyClass()
错误提示:

Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 15, in <module>
    myclass = MyClass()
              ^^^^^^^^^
TypeError: Can't instantiate abstract class MyClass with abstract method hello

原因是我们定义了抽像基类后,继承它的子类要实现所有基类里的抽象方法。 所以我们在子类中增加对基类中抽象方法hello的实现,就不会再报错了。

from abc import ABC, abstractmethod

class ABCBase(ABC):
    @abstractmethod
    def hello(self):
        pass

    def hi(self):
        print('基类 say hi')

class MyClass(ABCBase):
    def fun(self):
        print('this is fun')

    def hello(self):
        print('子类 say hello')

myclass = MyClass()
myclass.hello()
myclass.hi()

输出结果:

子类 say hello
基类 say hi

并且,只要是基类和子类的方法名相同,即便方法签名不同,也不报错。

from abc import ABC, abstractmethod

class ABCBase(ABC):
    @abstractmethod
    def hello(self, name):
        pass

    @abstractmethod
    def hi(self):
        print('基类 say hi')

class MyClass(ABCBase):
    def hello(self):
        print('子类 say hello')

    def hi(self, name):
        print('子类 say hi', name)

myclass = MyClass()
myclass.hello()
myclass.hi('Roland')

输出结果:

子类 say hello
子类 say hi Roland

抽象类不能被实例化。

from abc import ABC, abstractmethod

class ABCBase(ABC):
    @abstractmethod
    def hello(self, name):
        pass

abc_obj = ABCBase()

报错如下:

Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\forTest.py", line 8, in <module>
    abc_obj = ABCBase()
              ^^^^^^^^^
TypeError: Can't instantiate abstract class ABCBase with abstract method hello

抽象类的虚拟子类机制: 一个类可以不通过继承另一个类,但却能被issubclass函数判断该类为另一个类的子类。 我们说这个类其实是另一个类的虚拟子类。
先看一下实现这种虚拟子类机制的其中一种方式,使用__subclasshook__特殊方法。

from abc import ABC, ABCMeta

class MyABC(ABC):
    @classmethod
    def __subclasshook__(cls, subclass):
        # 如果子类有 'method1' 和 'method2' 属性或方法,就认为它是 MyABC 的子类
        if hasattr(subclass, 'method1') and hasattr(subclass, 'method2'):
            return True
        return NotImplemented

class SubClass:
    def method1(self):
        pass

    def method2(self):
        pass

class AnotherClass:
    def method1(self):
        pass

# 检查子类关系
print(issubclass(SubClass, MyABC))  # True
print(issubclass(AnotherClass, MyABC))  # False

subinstance = SubClass()
print(isinstance(subinstance, MyABC))  # True  构造虚拟子类的关系后,isinstance函数判断结果也会为True

当我们让该方法返回NotImplemented时,它就会转而遵循python默认的判断子类的机制。

另一种实现虚拟子类方法是ABC.register, 这个抽象基类方法来自于元类ABCMeta。这种方法更为简单直接。

from abc import ABC

class MyABC(ABC):
    pass

class Foo:
    pass

MyABC.register(Foo)  # 使用register方法将一个类直接注册为另一个抽象基类的虚拟子类

print(issubclass(Foo, MyABC))  # True

foo = Foo()
print(isinstance(foo, MyABC))  # True

abc.get_cache_token(): 用于获取当前的抽象基类(ABC)注册缓存的版本标记。它的主要目的是在动态注册虚拟子类时,提供一种机制来检测抽象基类的子类关系缓存是否发生了变化。
我们一般不会直接使用这个函数,可能在某些特别场景下,比如元编程时会用到。

from abc import ABC, get_cache_token

print('catch token is', get_cache_token())

class MyABC(ABC):
    pass

class MyClass:
    def method(self):
        print("Method in MyClass")

print('catch token is', get_cache_token())
# 注册 MyClass 为 MyABC 的虚拟子类
MyABC.register(MyClass)
print('catch token is', get_cache_token())

class Foo:
    pass

MyABC.register(Foo)
print('catch token is', get_cache_token())

class AnotherABC(ABC):
    pass

print('catch token is', get_cache_token())
AnotherABC.register(Foo)
print('catch token is', get_cache_token())

输出结果:

catch token is 25
catch token is 25
catch token is 26
catch token is 27
catch token is 27
catch token is 28

从以上示例代码的执行结果可以看出,abc.get_cache_token()会捕获每一次的虚拟子类的变化,并生成新的catch token。 注意,__subclasshook__不会影响cache token的值。

abc.update_abstractmethods(cls): 是 Python 的 abc 模块中的一个辅助工具,用于在运行时重新计算并更新类的抽象方法集合(即类的 abstractmethods 属性)
它在动态类生成、复杂继承关系的管理以及元编程中非常有用,但日常开发中较少用到。其主要作用是维护 abstractmethods 的正确性,确保类是否为抽象类的状态与其定义保持一致。
虽然官方文档没有明确提到 __abstractmethods__,但这个属性在 ABCMeta 中起到了关键作用。__abstractmethods__ 是由 ABCMeta 自动生成并维护的一个集合,包含所有未被实现的抽象方法。

import abc
from abc import ABC, abstractmethod, update_abstractmethods

class MyABC(ABC):
    @abstractmethod
    def method1(self):
        pass

# 一个子类,没有实现所有抽象方法
class MyClass(MyABC):
    pass


print('类定义后:', MyClass.__abstractmethods__)  # {'method1'}

# 动态添加一个抽象方法
def new_abstract_method(self):
    pass

MyClass.method2 = abstractmethod(new_abstract_method)

print('动态填加抽象方法method2,但未执行update前:', MyClass.__abstractmethods__)

# 更新抽象方法集合
update_abstractmethods(MyClass)
print('动态填加抽象方法method2,执行update后:', MyClass.__abstractmethods__)  # {'method1', 'method2'}

# 动态实现一个抽象方法
MyClass.method1 = lambda self: None
update_abstractmethods(MyClass)
print('动态填加普通方法method1,执行update后:', MyClass.__abstractmethods__)  # {'method2'}

del MyClass.method2
update_abstractmethods(MyClass)
print('动态删除method2方法,执行update后:', MyClass.__abstractmethods__)
print('vars(MyClass):', vars(MyClass))
myclass = MyClass() # 实例化 所有基类的抽象方法都实现了,所以可以正常创建实例对象

输出结果:

类定义后: frozenset({'method1'})
动态填加抽象方法method2,但未执行update前: frozenset({'method1'})
动态填加抽象方法method2,执行update后: frozenset({'method1', 'method2'})
动态填加普通方法method1,执行update后: frozenset({'method2'})
动态删除method2方法,执行update后: frozenset()
vars(MyClass): {'__module__': '__main__', '__doc__': None, '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object at 0x0000020F16565C40>, 'method1': <function <lambda> at 0x0000020F165639C0>}

从示例代码及输出结果可以发现,当我们动态修改类中的抽象方法后,如果不调用此方法,那么__abstractmethods__属性里的内容就不会更新,导致我们用类创建对象时,是否满足创建的条件的判断不准确。继承抽象类的子类必须要满足实现所有基类的抽象方法时才可以实例化。

抽象基类的方法中使用装饰器,并不会对子类的方法产生任何约束。

from abc import ABC, abstractmethod

class MyABC(ABC):
    @abstractmethod
    def my_abstract_method(self):
        pass

    @classmethod
    @abstractmethod
    def my_abstract_classmethod(cls):
        pass

    @staticmethod
    @abstractmethod
    def my_abstract_staticmethod():
        pass

    @property
    @abstractmethod
    def my_abstract_property(self):
        return 'abc'


class MyClass(MyABC):
    def my_abstract_method(self):
        pass

    def my_abstract_classmethod(cls):
        pass

    def my_abstract_staticmethod(self):
        pass

    def my_abstract_property(self):
        pass


obj = MyClass()  # 实例化时没报错,说明子类只要有相同的方法名即可,不用满足那些@property, @classmethod等等装饰器。
posted @ 2024-12-22 20:18  RolandHe  阅读(7)  评论(0编辑  收藏  举报