You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多模块下使用__init_subclass__出现异常行为的原因探究

Python模块导入与PEP-487子类注册的坑

问题场景

测试PEP-487的子类注册机制时,将代码拆分到两个模块后出现了不符合预期的结果:

代码示例

a.py

class PluginBase:
    subclasses = []
    def __init_subclass__(cls, **kwargs):
        print(f'__init_subclass__({cls!r}, **{kwargs!r})')
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

if __name__ == '__main__':
    from b import Plugin1, Plugin2
    print('a:', PluginBase.subclasses)

b.py

from a import PluginBase
class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

print('b:', PluginBase.subclasses)

第一次执行结果

运行python a.py后输出:

__init_subclass__(<class 'b.Plugin1'>, **{})
__init_subclass__(<class 'b.Plugin2'>, **{})
b: [<class 'b.Plugin1'>, <class 'b.Plugin2'>]
a: []

困惑点:b.py中打印的PluginBase.subclasses有内容,但a.py主程序里打印的却是空列表。即使把注册代码改成PluginBase.subclasses.append(cls)也没用,不过将a.py中的from b import Plugin1, Plugin2改成from b import *后,就能得到预期的结果:

__init_subclass__(<class 'b.Plugin1'>, **{})
__init_subclass__(<class 'b.Plugin2'>, **{})
b: [<class 'b.Plugin1'>, <class 'b.Plugin2'>]
a: [<class 'b.Plugin1'>, <class 'b.Plugin2'>]

疑问

  1. 为何要使用cls.subclasses.append而非PluginBase.subclasses.append?
  2. 此场景下from b import *与from b import Plugin1, Plugin2的差异是什么?

解答

问题1:cls.subclasses 和 PluginBase.subclasses的区别

核心原因是Python模块导入时的双重实例问题:

  • 当你执行python a.py时,a模块的__name__是__main__,此时定义的PluginBase是__main__.PluginBase。
  • 而b模块导入a时,Python会加载一个独立的a模块实例(命名为a,而非__main__),b里继承的是a.PluginBase,这和主程序里的__main__.PluginBase是完全不同的类对象。

不管你写cls.subclasses.append(cls)还是PluginBase.subclasses.append(cls),只要b里的子类继承的是a.PluginBase,操作的都是a.PluginBase.subclasses列表;而a主程序里打印的是__main__.PluginBase.subclasses,自然是空的。

cls.subclasses的写法其实更健壮:如果后续有子类自己定义了subclasses属性,会优先使用子类的;如果没有,才会向上查找父类的subclasses,这符合继承的属性查找逻辑。

问题2:两种导入方式的差异

差异的本质是是否覆盖了主程序中的PluginBase对象:

  • 用from b import Plugin1, Plugin2时,只导入了两个子类,主程序里的PluginBase还是原来的__main__.PluginBase,和b里的a.PluginBase不是同一个对象,所以打印的subclasses为空。
  • 用from b import *时,会导入b模块中所有不以_开头的名称,其中包括b模块里导入的a.PluginBase。这个PluginBase会直接覆盖主程序里原来的__main__.PluginBase,此时打印的PluginBase.subclasses其实是a.PluginBase.subclasses——也就是b模块中已经添加了子类的那个列表,所以显示有内容。

内容的提问来源于stack exchange,提问作者FriendFX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 03:45:07