我有以下代码来将所需函数的基类的当前(空)实现与其子类进行比较,子类必须以某种不同的方式实现它们,以便在运行时被认为是可接受的。如果不使用 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__
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.
因此,在保持主要方法的同时,整个事情可以变得非常简单:
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/