如何使用__init_subclass__而不是ABCMeta来实现子类以实现父类的抽象方法? [英] How to enforce a subclass to implement a parent class' abstract methods using __init_subclass__ instead of ABCMeta?

查看:116
本文介绍了如何使用__init_subclass__而不是ABCMeta来实现子类以实现父类的抽象方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have the following code to compare a base class' current (empty) implementation of required functions to its sub-classes, which must implement them in some different way in order to be considered acceptable at runtime. Without using a metaclass=ABCMeta and implementing @abstractmethod decorators on these base class methods, how do I go about doing this? For now, I'm writing the following __init_subclass__ hook on my ad-hoc, metaclass-less abstract base classes in multiple places in my project, but it feels wrong.

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

Is there a better way to do this? Bonus points if I can get typechecking errors in my editor while defining sub classes of this AbstractThing.

推荐答案

的确,您不应依赖 inspect.getsourcelines 来获取应在严重情况下使用的任何代码(例如,超出实验领域或处理工具的代码) (带有源代码本身)

Indeed, you should not rely on inspect.getsourcelines for any code that should be used in serious contexts (i.e. outside experimentation realm, or tools to deal with the source-code itself)

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

The plain and simple is operator is enough to check if the method in a given class is the same as in the base class. (In Python 3. Python 2 users have to take care that methods are retrieved as unbound methods instead of the raw-functions)

除此之外,您需要进行一些不必要的操作才能进入基类本身-很少记录且很少使用的特殊变量 __ class __ 可以帮助您:它是对编写它的类体(不要误以为 self .__ class __ ,它是对子类的引用)。

Other than that, you are taking several unneeded turns to get to the base-class itself - the little documented and little used special variable __class__ can help you with that: it is an automatic reference to the class body where it is written (do not mistake with self.__class__ which is a reference to the sub-class instead).

从文档中:


该类对象是零参数形式的<$ c引用的对象。 $ c> super()。 __class __ 是由编译器创建的隐式闭包引用,如果类主体中的任何方法引用 __ class __ super 这允许 super()的零参数形式根据词法作用域正确识别正在定义的类,而使用的类或实例

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.

因此,在保留您的主要方法的同时,整个调用将根据传递给该方法的第一个参数进行标识。事情可能非常简单:

So, while keeping your main approach, the whole thing can be quite simpler:

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 中硬编码所需的方法,仍然可以使用抽象方法装饰器m abc ,而不使用 ABCMeta 元类。装饰者所做的只是在对元类检查的方法上创建一个属性。只需在 __ init_subclass __ 方法中进行相同的检查即可:

If you have a complex hierarchy, and will have parent classes with other mandatory methods that the subclasses of those will have to implement - and therefore, can't hard code the needed methods in the required_methods, you can still use the abstractmethod decorator from abc, without using the ABCMeta metaclass. All the decorator does is to create an attribute on the method that is checked on the metaclass. Just make the same check in a __init_subclass__ method:

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 __ 的使用足以使它变得可靠-小心记录一下。

Keep in mind this just checks for methods that show up in a class' dir. But customizing __dir__ is used seldon enough for it to be reliable - just take care to document that.

这篇关于如何使用__init_subclass__而不是ABCMeta来实现子类以实现父类的抽象方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆