python - 如何强制子类使用 __init_subclass__ 而不是 ABCMeta 来实现父类的抽象方法?

标签 python abstract-class inspect

我有以下代码来将所需函数的基类的当前(空)实现与其子类进行比较,子类必须以某种不同的方式实现它们,以便在运行时被认为是可接受的。如果不使用 metaclass=ABCMeta 并在这些基类方法上实现 @abstractmethod 装饰器,我该如何去做呢?目前,我正在项目中多个位置的临时无元类抽象基类上编写以下 __init_subclass__ Hook ,但感觉不对。

import inspect

class AbstractThing:
    def __init__(self, topic: str, thing: Thing):
        thing.subscriptions[topic] = self.on_message
        thing.on_connected.append(self.on_connected)
        thing.on_disconnected.append(self.on_disconnected)

    def __init_subclass__(cls):
        required_methods = ['on_connected', 'on_disconnected', 'on_message']
        for f in required_methods:
            func_source = inspect.getsourcelines(getattr(cls, f))
            # if this class no longer inherits from `Object`, the method resolution order will have updated
            parent_func_source = inspect.getsourcelines(getattr(cls.__mro__[-2], f))
            if func_source == parent_func_source:
                raise NotImplementedError(f"You need to override method '{f}' in your class {cls.__name__}")

    def on_connected(self, config: dict):
        pass

    def on_disconnected(self):
        pass

    def on_message(self, msg: str):
        pass

有更好的方法吗?如果我在定义此 AbstractThing 的子类时能够在编辑器中出现类型检查错误,那就加分了。

最佳答案

事实上,您不应该依赖 inspect.getsourcelines 来获取应在严肃上下文中使用的任何代码(即外部实验领域,或处理源代码本身的工具)

简单明了的 is 运算符足以检查给定类中的方法是否与基类中的方法相同。 (在 Python 3 中。Python 2 用户必须注意方法是作为未绑定(bind)方法而不是原始函数检索的)

除此之外,您还需要经过几次不必要的轮转才能到达基类本身 - little documented and little used special variable __class__可以帮助您:它是对编写它的类主体的自动引用(不要与 self.__class__ 混淆,它是对子类的引用)。

来自文档:

This class object is the one that will be referenced by the zero-argument form of super(). __class__ is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__ or super. This allows the zero argument form of super() to correctly identify the class being defined based on lexical scoping, while the class or instance that was used to make the current call is identified based on the first argument passed to the method.

因此,在保持主要方法的同时,整个事情可以变得非常简单:

def __init_subclass__(cls):
    required_methods = ['on_connected', 'on_disconnected', 'on_message']
    for f in required_methods:
         if getattr(cls, f) is getattr(__class__, f):
              raise NotImplementedError(...)

如果您有一个复杂的层次结构,并且父类具有其他强制方法,这些方法的子类必须实现这些方法 - 因此,无法在 required_methods 中硬编码所需的方法,您仍然可以使用 abc 中的 abstractmethod 装饰器,而不使用 ABCMeta 元类。装饰器所做的就是在元类上检查的方法上创建一个属性。只需在 __init_subclass__ 方法中进行相同的检查即可:

from abc import abstractmethod

class Base:
   def __init_subclass__(cls, **kw):
        super().__init_subclass__(**kw)
        for attr_name in dir(cls):
            method = getattr(cls, attr_name)
            if (getattr(method, '__isabstractmethod__', False) and
                    not attr_name in cls.__dict__):
                # The second condition above allows 
                # abstractmethods to exist in the class where 
                # they are defined, but not on further subclasses
                raise NotImplementedError(...)

class NetworkMixin(Base):
    @abstractmethod
    def on_connect(self):
         pass

class FileMixin(Base):
    @abstractmethod
    def on_close(self):
         pass

class MyFileNetworkThing(NetworkMixin, FileMixin):
    # if any of the two abstract methods is not
    # implemented, Base.__init_subclass__ will fail

请记住,这只是检查类dir中显示的方法。但是自定义 __dir__ 的使用很少,因此它是可靠的 - 只需注意记录即可。

关于python - 如何强制子类使用 __init_subclass__ 而不是 ABCMeta 来实现父类的抽象方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54439861/

相关文章:

docker - 我怎样才能找出容器被赋予了哪些功能?

C代码的Python翻译不起作用

python - 在不创建多个脚本的情况下同时运行多个 python 脚本

python - 使用 5 的倍数作为变量可以更轻松地使用模数

swift - ARKit – 光估计

debugging - 强制显示 PureScript 中的记录

Python 字符串格式为 float ,如何截断而不是舍入

c# - new() 的通用类型约束和抽象基类

具有抽象类指针的对象的 C++ 拷贝

python - 从 pytest 断言中解析出数据并转换为字符串