如何使用__init_subclass__而不是ABCMeta来实现子类以实现父类的抽象方法? [英] How to enforce a subclass to implement a parent class' abstract methods using __init_subclass__ instead of 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__
orsuper
. This allows the zero argument form ofsuper()
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屋!