在多个模块中使用 __init_subclass__ 的奇怪行为

问题描述 投票:0回答:1

受到有关插件架构的答案的启发,我正在研究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'>]

有人可以启发我吗

  1. 为什么我们写
    cls.subclasses.append
    而不是
    PluginBase.subclasses.append
  2. 在这种情况下,
    from b import *
    from b import Plugin1, Plugin2
    之间有什么区别?
python python-import subclass
1个回答
0
投票

您的入口点不应由其他模块导入。这会导致该入口点独立加载两次,一次作为

__main__
(最初执行的模块),另一个作为
a
(当从
b.py
导入模块时)。

通常,当您

import
另一个模块中的模块时,会通过
sys.modules
通过 name 查找该模块,如果已经导入,则只需检索该模块,而不是在每次某个模块使用其他模块时重新创建。但是入口点作为
sys.modules
存在于
sys.modules['__main__']
中,并且当
import
出现在
b.py
中时,导入系统找不到
sys.modules['a']
,它会创建一个 新模块

© www.soinside.com 2019 - 2024. All rights reserved.