如何强制子 class 使用 __init_subclass__ 而不是 ABCMeta 来实现父 class 的抽象方法?
How to enforce a subclass to implement a parent class' abstract methods using __init_subclass__ instead of ABCMeta?
我有以下代码来比较基本 class 所需功能的当前(空)实现与其子 classes,后者必须以某种不同的方式实现它们才能在运行时被认为是可接受的。如果不使用 metaclass=ABCMeta
并在这些基础 class 方法上实现 @abstractmethod
装饰器,我该怎么做呢?现在,我在项目的多个地方为我的特设、无元class 的抽象基础 classes 写了以下 __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
.
的子 class 时在我的编辑器中出现类型检查错误,则可加分
事实上,您不应该依赖 inspect.getsourcelines
任何应该在严肃环境中使用的代码(即实验领域之外,或处理源代码本身的工具)
简单明了的 is
运算符足以检查给定 class 中的方法是否与基础 class 中的方法相同。 (在 Python 3. Python 2 中,用户必须注意将方法检索为 unbound methods
而不是原始函数)
除此之外,您需要进行几次不必要的转弯才能到达基地 - class 本身 - little documented and little used special variable __class__
可以帮助您:它是对 [=46] 的自动引用=] 写的正文(不要误认为 self.__class__
是对子 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__
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.
因此,在保持主要方法的同时,整个事情可以变得非常简单:
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(...)
如果您有一个复杂的层次结构,并且父 class 具有其他强制性方法,这些方法的子 class 必须实现 - 因此,不能硬编码required_methods
中所需的方法,您仍然可以使用 abc
中的 abstractmethod
装饰器,而无需使用 ABCMeta
元 class。装饰器所做的只是在元 class 上检查的方法上创建一个属性。只需在 __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
请记住,这只是检查 class' dir
中出现的方法。但是自定义 __dir__
很少被使用以使其可靠 - 只需注意记录它。
我有以下代码来比较基本 class 所需功能的当前(空)实现与其子 classes,后者必须以某种不同的方式实现它们才能在运行时被认为是可接受的。如果不使用 metaclass=ABCMeta
并在这些基础 class 方法上实现 @abstractmethod
装饰器,我该怎么做呢?现在,我在项目的多个地方为我的特设、无元class 的抽象基础 classes 写了以下 __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
运算符足以检查给定 class 中的方法是否与基础 class 中的方法相同。 (在 Python 3. Python 2 中,用户必须注意将方法检索为 unbound methods
而不是原始函数)
除此之外,您需要进行几次不必要的转弯才能到达基地 - class 本身 - little documented and little used special variable __class__
可以帮助您:它是对 [=46] 的自动引用=] 写的正文(不要误认为 self.__class__
是对子 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(...)
如果您有一个复杂的层次结构,并且父 class 具有其他强制性方法,这些方法的子 class 必须实现 - 因此,不能硬编码required_methods
中所需的方法,您仍然可以使用 abc
中的 abstractmethod
装饰器,而无需使用 ABCMeta
元 class。装饰器所做的只是在元 class 上检查的方法上创建一个属性。只需在 __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
请记住,这只是检查 class' dir
中出现的方法。但是自定义 __dir__
很少被使用以使其可靠 - 只需注意记录它。