繁体   English   中英

__subclasses__ 还是通过 __init_subclass__ 注册的?

[英]__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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM