![](/img/trans.png)
[英]Registry pattern with __init_subclass__ and sub-classable registry
[英]__subclasses__ or registry via __init_subclass__?
假设我想创建某个 class 的子类的注册表。 现在有两种方法我可以想到,虽然我知道它们的(一些)差异,但我很想了解更多关于这个主题的信息。
class Base:
pass
class DerivedA(Base):
pass
class DerivedB(Base):
pass
__subclasses__()
如果我有上述情况,我可以像这样简单地获取Base
的子类列表:
>>> [cmd.__name__ for cmd in Base.__subclasses__()]
['DerivedA', 'DerivedB']
现在我知道,如果我添加第三个 class 不是像这样直接继承Base
的:
class DerivedC(DerivedA):
pass
我不会在列表中看到这个:
>>> [cmd.__name__ for cmd in Base.__subclasses__()]
['DerivedA', 'DerivedB']
此外,我无法过滤子类,例如出于任何原因忽略特定子类。
__init_subclass__()
由于 Python 3.6 有一个很好的钩子到 class 创建过程,更高级的事情可以在不编写自己的元类的情况下完成。 因此我也可以做这样的事情......
_registry = []
class Base:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
_registry.append(cls.__name__)
class DerivedA(Base):
pass
class DerivedB(Base):
pass
class DerivedC(DerivedA):
pass
然后只需访问_registry
:
>>> _registry
['DerivedA', 'DerivedB', 'DerivedC']
如果需要,我还可以修改Base
以忽略某些子类:
_registry = []
class Base:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if cls.__name__ != 'DerivedB':
_registry.append(cls.__name__)
class DerivedA(Base):
pass
class DerivedB(Base):
pass
class DerivedC(DerivedA):
pass
>>> _registry
['DerivedA', 'DerivedC']
现在假设我不想过滤子类,我只对直接子类感兴趣。 前一种方法似乎更简单(我知道是主观的)。 还有哪些其他区别,后一种方法的优点可能是什么?
谢谢!
在这种情况下,在基础 class 中写入__init_subclass__
的明显好处是,您可以自动获取不直接从基础 class 继承的子类,正如您所说的那样。
如果你只需要直接从你的基类继承的类,那么它在__subclasses__
方法中已经准备好了,主要优点是你不需要编写一行代码,甚至不需要保留一个单独的注册表,因为__subclasses__
将为您做到这一点。
但是,除非您正在编写一个相对较小的应用程序,或者正在处理只需要查找少量固定数量的这些子类的功能,否则依赖__subclasses__
是不够的 - 如果您只是需要或想要另一个级别层次结构中的类,它将停止工作,无论如何您都必须求助于真正的注册表。
在使用__init_subclass__
钩子之前,必须编写一个适当的元类来保存这个注册表,将它提供给元类__init__
方法,或者执行一个复杂的递归查询,例如:
def check_subclass(base, candidate):
for cls in base.__subclasses__():
if cls is candidate:
return True
if check_subclass(cls, candidate):
return True
return False
而且,虽然它应该 go 不用说,但__init_subclass__
方法可以做的不仅仅是保留一个注册表 - 因为它可以运行任何代码。 如果映射到该子类的字段是最新的,它可以检查 DB 层,并警告需要迁移 - 甚至执行 DB 迁移本身,或者初始化 class 的实例需要准备就绪的任何资源被创建,例如 logger-handlers、thread-pools、db-connection pools,你可以命名它。
TL;DR:如果您只需要 class、go 的直接子类和__subclasses__
。 问题在于它只是注释了直接子类。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.