我有一个基类,以及从中继承的几个子类。我试图动态地检测哪些子类动态地从基类继承。我目前正在通过动态导入基类__init__()
中的所有子类,然后使用__subclasses__()
方法来完成它。
我有以下文件结构:
proj/
|-- __init__.py
|-- base.py
`-- sub
|-- __init__.py
|-- sub1.py
|-- sub2.py
`-- sub3.py
base.py:
import importlib
class Base(object):
def __init__(self):
importlib.import_module('sub.sub1')
importlib.import_module('sub.sub2')
importlib.import_module('sub.sub3')
@classmethod
def inheritors(cls):
print(cls.__subclasses__())
b = Base()
b.inheritors()
sub1.py:
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from base import Base
class Sub1(Base):
pass
sub2.py:
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from base import Base
class Sub2(Base):
pass
最后是sub3.py:
import sys
import os
class Sub3(object):
pass
您会注意到sub.sub1.Sub1
和sub.sub2.Sub2
都从base.Base
继承而sub.sub3.Sub3
没有。
当我打开IPython3并运行import base
时,我得到以下输出:
In [1]: import base
[<class 'sub.sub1.Sub1'>, <class 'sub.sub2.Sub2'>]
上面的输出完全符合我的预期。当我使用Python命令行运行base.py时,它会变得奇怪:
python3 base.py
[<class 'sub.sub2.Sub2'>]
[]
现在我认为我理解在第二种情况下有两个打印,因为Python导入器最初在base.py
全局变量中没有看到sys.modules
,因此当导入子类时它将导入再次base.py
,代码将再次执行。这个解释并没有解释为什么第一次打印[<class 'sub.sub2.Sub2'>]
而不是[<class 'sub.sub1.Sub1'>]
作为sub.sub1.Sub1
首先被导入,并且它没有解释为什么只有sub.sub2.Sub2
出现在{{{ 1}}而__subclasses__()
没有。
任何可以帮助我理解Python如何在这方面工作的解释将非常感谢!
编辑:我想使用sub.sub1.Sub1
运行模块,所以也许我可以指出正确的方向?
答案 0 :(得分:3)
你结了。 一个复杂的,不需要的结。我可以弄清楚 - 但我不知道我是否可以记住它以明确的方式解释发生了什么: - )
但首先要做的一件事:这与“继承检测”关系不大,而与导入系统有关 - 这与你在一个复杂的结中捆绑在一起。
因此,您得到了意外的结果,因为当您执行python base.py
时,base的内容将被记录为__main__
中名为sys.modules
的模块。
通常,Python永远不会导入模块并再次运行相同的代码:在创建尝试导入现有模块的import语句时,它只会创建一个与现有模块相关的新变量。如果该模块尚未完成其主体的执行,则并非所有类或变量都出现在存在第二个import语句的位置。对importlib的调用不会更好 - 他们只是不会自动化变量biding部分。当您执行循环导入时,更改导入路径,并从另一个文件导入名为base
的模块,Python不知道base
是__main__
base
。因此,新的获取新的导入,并在sys.modules中获得第二个条目,__class__
。
如果您只是在您的继承者方法中打印@classmethod
def inheritors(cls):
print("At class {}. Subclasses: {}".format(__class__, cls.__subclasses__()))
,那么很明显:
__main__.Base
然后你会看到“base.Base”有“sub2”子类,base.py
没有子类。
现在,让我试着为它设定时间表:
__main__
导入为b =
Base()
并向上运行到__init__
行。此时Base的sub1
方法将导入
子模块base
,更改sys.path,和
重新导入base.py作为__init__
模块。 sub.sub1
方法为止;
其中,它导入sys.modules
,Python发现这个模块有
已导入并位于Sub1
。它的代码还没有
已完成,但__init__
基数尚未定义。 sub.sub2
尝试导入sub2
。那
是Python的新模块,因此导入import base
,当满足base.Base
时,Python会将模块识别为
已导入(虽然,并非所有初始化代码
完成) - 它只是为sub2全局变量带来名称别名,并且
sub.sub2
__init__
导入完成,Python继续执行步骤(4)中的b.inheritors()
方法; Python导入sub.sub3并恢复到base
调用
(来自main
,而非来自base.Base
)。此时唯一的子类
sub2
是base.py
- 已打印base
完成sub.sub1
后,Python继续执行bodu
Sub1
- 类base.Base
被定义为__main__.base.__init__
sub.sub3
执行,导入
sub.sub2 - 但它已经运行,__main__.Base.inheritors
__main__
中调用sys.path.append
,并打印否
的子类。这是复杂历史的终结。
首先:如果您需要执行proj
欺骗,那么您的软件包就会出现问题。如果您希望运行(并动态导入其他模块),请将您的包设为proj.__init__
,并指向base
以导入cls.__subclasses__
- 但停止使用sys.path查找内容在你自己的包中。
第二:
cls
调用没什么用处,因为它只会告诉你__init_subclass__
的中间子类 - 如果有一个宏大的chid子类它会变成unoticed,
最常见的模式是拥有Base的子类的注册表 - 在创建它们时,只需将新类添加到此记录中。这可以用Python中的元类来完成。 3.6,或者使用Python 3.6及其上的GotFocus
方法。