受到有关插件架构的答案的启发,我正在研究PEP-487的子类注册,发现稍微更改代码会产生令人惊讶的结果。
第一步是将上面链接的答案中的代码拆分为两个文件:
$ cat 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)
$ cat 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: []
我发现这个输出令人惊讶,为什么从
a.py打印时
PluginBase
的 subclasses
列表为空,而不是从 b.py 打印?
直观地说,我会在 a.py 中将子类注册行写为
PluginBase.subclasses.append(cls)
而不是
cls.subclasses.append(cls)
因为我想对
PluginBase
的 subclass
字段而不是相应的 Plugin*
进行操作,但仅此一点也没有给出预期的结果。
然后我发现只需替换 a.py 的行即可修复该行为
from b import Plugin1, Plugin2
到
from b import *
当执行a.py时,会产生我期望的输出,即
$ python a.py
__init_subclass__(<class 'b.Plugin1'>, **{})
__init_subclass__(<class 'b.Plugin2'>, **{})
b: [<class 'b.Plugin1'>, <class 'b.Plugin2'>]
a: [<class 'b.Plugin1'>, <class 'b.Plugin2'>]
有人可以启发我吗
cls.subclasses.append
而不是PluginBase.subclasses.append
和from b import *
和 from b import Plugin1, Plugin2
之间有什么区别?您的入口点不应由其他模块导入。这会导致该入口点独立加载两次,一次作为
__main__
(最初执行的模块),另一个作为a
(当从b.py
导入模块时)。
通常,当您
import
另一个模块中的模块时,会通过 sys.modules
通过 name 查找该模块,如果已经导入,则只需检索该模块,而不是在每次某个模块使用其他模块时重新创建。但是入口点作为 sys.modules
存在于 sys.modules['__main__']
中,并且当 import
出现在 b.py
中时,导入系统找不到 sys.modules['a']
,它会创建一个 新模块。