我有以下代码来比较基类的电流(空)执行所需的功能,以它的子类,其中必须实现他们在某些不同的方式,以被认为是在运行时是可接受的。如果不使用metaclass=ABCMeta
和实施@abstractmethod
装饰这些基类的方法,我怎么会去这样做?现在,我正在写在我的项目中多个地方我的ad-hoc,元类,那么抽象基类以下__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
的子类。
事实上,你不应该依赖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
所需要的方法,你仍然可以使用从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
显示方法。但是,定制__dir__
用于塞尔顿足以使它成为可靠的 - 只是要小心文件证明。