ZetCode

Python __init_subclass__ 方法

最后修改于 2025 年 4 月 8 日

本综合指南探讨了 Python 的 __init_subclass__ 方法,它是一个用于自定义子类创建的钩子。我们将涵盖基本用法、类注册、插件系统、验证和实践示例。

基本定义

__init_subclass__ 方法在类被子类化时调用。 它提供了一种在不使用元类的情况下自定义子类创建的方法。

主要特点:它是一个类方法(但不需要装饰器),接收子类作为参数,并在类定义期间运行。 在 Python 3.6 中引入,作为许多用例中元类的一种更简单的替代方法。

基本的 __init_subclass__ 实现

这是最简单的实现,展示了 __init_subclass__ 的工作原理。 它演示了基本结构以及何时调用它。

basic_init_subclass.py
class Base:
    def __init_subclass__(cls, **kwargs):
        print(f"Subclass {cls.__name__} created")
        super().__init_subclass__(**kwargs)

class Child(Base):
    pass

# Output: "Subclass Child created"

此示例显示了基本模式。 当 Child 继承自 Base 时,会自动调用 __init_subclass__,并将 Child 类作为参数传递。

**kwargs 捕获类定义中传递的任何其他关键字参数。 始终调用 super() 以支持协作继承。

类注册模式

__init_subclass__ 通常用于实现类注册,其中子类由父类自动跟踪。

registration.py
class PluginRegistry:
    plugins = []
    
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        PluginRegistry.plugins.append(cls)

class PluginA(PluginRegistry):
    pass

class PluginB(PluginRegistry):
    pass

print(PluginRegistry.plugins)  # [<class '__main__.PluginA'>, <class '__main__.PluginB'>]

此实现自动收集所有继承自 PluginRegistry 的插件类。 然后,可以使用注册表来实例化或检查插件。

这种模式对于插件系统非常有用,在插件系统中,您希望发现所有可用的实现,而无需显式注册它们。

添加类属性

__init_subclass__ 可以在创建期间通过添加或修改类属性来修改子类。

add_attributes.py
class Document:
    def __init_subclass__(cls, doc_type=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if doc_type is not None:
            cls.doc_type = doc_type

class PDF(Document, doc_type="application/pdf"):
    pass

class Word(Document, doc_type="application/msword"):
    pass

print(PDF.doc_type)  # "application/pdf"
print(Word.doc_type)  # "application/msword"

此示例显示了如何将类型信息添加到文档类。 doc_type 参数在类定义期间传递。

该方法检查参数,如果存在则将其添加为类属性。 这种模式对于声明式类定义非常有用。

子类验证

__init_subclass__ 可以验证子类定义,确保它们满足某些要求。

validation.py
class Shape:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if not hasattr(cls, 'area'):
            raise TypeError(f"Subclass {cls.__name__} must implement 'area' method")

class Circle(Shape):
    def area(self):
        return 3.14 * self.radius ** 2

# class Square(Shape):  # Would raise TypeError
#     pass

此实现确保所有 Shape 子类都实现一个 area 方法。 检查发生在类创建期间,如果未满足要求,则快速失败。

这比在运行时检查更易于维护,并在开发期间提供更清晰的错误消息。

具有配置的插件系统

更高级的插件系统可以使用 __init_subclass__ 来处理插件配置和注册。

plugin_system.py
class PluginSystem:
    _plugins = {}
    
    def __init_subclass__(cls, name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if name is None:
            name = cls.__name__.lower()
        if name in PluginSystem._plugins:
            raise ValueError(f"Plugin {name} already exists")
        PluginSystem._plugins[name] = cls
    
    @classmethod
    def get_plugin(cls, name):
        return cls._plugins.get(name)

class DatabasePlugin(PluginSystem, name="db"):
    pass

class CachePlugin(PluginSystem, name="cache"):
    pass

print(PluginSystem.get_plugin("db"))  # <class '__main__.DatabasePlugin'>

此实现添加了命名注册和重复预防。 插件必须提供唯一的名称,这些名称用于稍后检索它们。

该系统演示了 __init_subclass__ 如何管理更复杂的注册场景,同时保持类定义的简洁。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程