__subclasses__() 不返回动态导入的子类,因其仅返回已加载且完成定义的直接子类;应改用 __init_subclass__ 隐式注册或装饰器显式注册,配合工厂函数安全实例化。

为什么 __subclasses__() 不能直接用在动态导入后
Python 的 __subclasses__() 只返回当前已加载且继承自该类的子类,但它不感知后续 import 或热重载。如果你在主模块里定义基类,然后在其他文件中写子类,但那些文件没被导入过,__subclasses__() 就为空——这不是 bug,是 Python 类加载机制决定的。
常见错误现象:BaseHandler.__subclasses__() 返回 [],哪怕子类文件存在且语法正确;或者只返回部分子类,因为某些模块因异常没执行完类定义。
- 必须确保子类模块已被 import(哪怕只是
import myapp.handlers.json_handler) - 避免在子类模块顶层触发未处理异常,否则类定义不会完成,不会进入基类的子类链
-
__subclasses__()不递归查找孙子类,只查直接子类
用类装饰器 + 注册表实现显式自动注册
比依赖 __subclasses__() 更可靠的方式,是让子类“主动报到”。通过一个轻量级类装饰器,在类定义完成后立即将其加入全局注册表,不依赖导入顺序或模块执行时机。
典型使用场景:插件系统、序列化器选择(如根据 format="xml" 找 XMLSerializer)、命令行子命令发现。
立即学习“Python免费学习笔记(深入)”;
- 注册表建议用字典,键为字符串标识(如
cls.name或cls.__name__.lower()),值为类本身 - 装饰器要加
@classmethod或检查inspect.isclass(obj),防止误装饰函数 - 避免在装饰器里做耗时操作(如 I/O),否则会拖慢模块加载
示例:
registry = {}
<p>def register(name=None):
def decorator(cls):
key = name or cls.<strong>name</strong>.lower()
registry[key] = cls
return cls
return decorator</p><p>@register("csv")
class CSVExporter:
pass</p><p>@register()
class JSONExporter:
pass</p><h1>使用:registry["csv"] → <class 'CSVExporter'></h1><p>利用 __init_subclass__ 做隐式自动注册(Python 3.6+)
这是最干净的方案:基类定义 __init_subclass__,所有继承它的类在定义时自动触发注册逻辑,无需装饰器、无需手动 import 触发,只要类被 Python 解析就生效。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
注意点:
-
__init_subclass__是类方法,第一个参数是子类cls,不是实例;它在class语句执行完毕后立即调用 - 不能用于已存在的类(即不能 retroactively 注册),只对之后定义的子类有效
- 如果基类自身有
__init_subclass__,子类再继承时不会自动继承该方法——它不是普通方法,不参与 MRO 查找
示例:
class Plugin:
_registry = {}
<pre class="brush:php;toolbar:false;">def __init_subclass__(cls, name=None, **kwargs):
super().__init_subclass__(**kwargs)
# 自动注册,支持显式 name=... 覆盖默认键
key = name or cls.__name__.lower()
Plugin._registry[key] = clsclass HTTPFetcher(Plugin, name="http"): pass
class FTPFetcher(Plugin): pass
Plugin._registry == {"http": , "ftpfetcher": }
注册后如何安全地实例化或调用
注册只是第一步,真正使用时容易踩坑的是类型校验和初始化参数传递。不要假设所有注册类都有相同构造签名,也不要直接 cls() —— 有些需要配置、有些是抽象基类。
常见错误现象:TypeError: __init__() missing 2 required positional arguments;或拿到类却忘了它是 ABC,不能实例化。
- 注册前可加白名单检查:
if not issubclass(cls, abc.ABC),或要求实现某个标记属性(如cls.is_plugin = True) - 推荐注册时存元数据,比如
cls._plugin_metadata = {"version": "1.2", "requires_auth": True},便于运行时决策 - 实例化建议封装一层工厂函数,统一处理参数、异常、上下文注入
例如:
def get_plugin(name, **init_kwargs):
cls = Plugin._registry.get(name)
if cls is None:
raise ValueError(f"Unknown plugin: {name}")
if not hasattr(cls, "run"):
raise TypeError(f"{cls} has no 'run' method")
return cls(**init_kwargs)
注册逻辑本身很简单,难的是怎么让它在真实项目里不掉链子——模块循环导入时注册时机错乱、测试环境反复 reload 导致重复注册、子类覆盖了基类的 __init_subclass__ 却忘了调用 super()。这些地方没日志、没断言,问题会藏得很深。

















