【问题标题】:How to enforce a subclass to implement a parent class' abstract methods using __init_subclass__ instead of ABCMeta?如何使用 __init_subclass__ 而不是 ABCMeta 强制子类实现父类的抽象方法?
【发布时间】:2023-04-06 23:57:01
【问题描述】:

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

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 的子类时在编辑器中遇到类型检查错误,则可以加分。

【问题讨论】:

    标签: python abstract-class inspect


    【解决方案1】:

    确实,您不应该依赖inspect.getsourcelines 来处理任何应该在严肃的环境中使用的代码(即,在实验领域之外,或处理源代码本身的工具)

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

    除此之外,您还需要进行几次不必要的轮换才能获得基类本身 - little documented and little used special variable __class__ 可以帮助您:它是对编写它的类主体的自动引用(不要误认为self.__class__ 是对子类的引用)。

    来自文档:

    这个类对象将被super(). __class__ 的零参数形式引用,如果类主体中的任何方法引用__class__ 或super,则编译器会创建一个隐式闭包引用。 这允许super() 的零参数形式根据词法范围正确识别正在定义的类,而用于进行当前调用的类或实例是根据传递的第一个参数来识别的方法。

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

    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__ 以使其可靠 - 请注意记录它。

    【讨论】:

    • 我真的很想使用is,但是就像你说的,我无法在不摸索方法解析顺序的情况下进入父类,因为我不知道@的存在987654322@,谢谢。
    猜你喜欢
    • 2013-08-02
    • 2012-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-13
    • 2010-12-18
    • 2012-03-08
    • 1970-01-01
    相关资源
    最近更新 更多