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

时间:2019-01-30 11:43:37

标签: python abstract-class inspect

我有以下代码将基本类的当前(空)所需函数的实现与其子类进行比较,子类必须以某种不同的方式实现它们,以便在运行时被接受。在这些基类方法上不使用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的子类时在编辑器中遇到类型检查错误。

1 个答案:

答案 0 :(得分:2)

实际上,您不应依赖inspect.getsourcelines来处理应在严重上下文中使用的任何代码(例如,在实验领域之外或用于处理源代码本身的工具)

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

除此之外,您需要进行一些不必要的操作才能进入基类本身-little documented and little used special variable __class__可以帮助您:它是对编写它的类主体的自动引用(不要self.__class__错误,而该错误是对子类的引用。

从文档中:

  

该类对象是将由{_1}的零参数形式引用的对象,如果类主体中的任何方法引用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中的abstractmethod装饰器,而不使用abc元类。装饰者所做的只是在对元类检查的方法上创建一个属性。只需在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的使用足以使它变得可靠-务必要对其进行记录。