Python继承检测如何工作?

时间:2017-03-19 18:55:19

标签: python inheritance python-import

我有一个基类,以及从中继承的几个子类。我试图动态地检测哪些子类动态地从基类继承。我目前正在通过动态导入基类__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.Sub1sub.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运行模块,所以也许我可以指出正确的方向?

1 个答案:

答案 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没有子类。

现在,让我试着为它设定时间表:

  1. __main__导入为b = Base()并向上运行到__init__行。此时Base的sub1方法将导入 子模块
  2. 运行子模块base,更改sys.path,和 重新导入base.py作为__init__模块。
  3. 的内容 运行基本模块,直到满足base.Base中的sub.sub1方法为止; 其中,它导入sys.modules,Python发现这个模块有 已导入并位于Sub1。它的代码还没有 已完成,但__init__基数尚未定义。
  4. 在base1的sub1导入中,sub.sub2尝试导入sub2。那 是Python的新模块,因此导入
  5. 关于进口 import base,当满足base.Base时,Python会将模块识别为 已导入(虽然,并非所有初始化代码 完成) - 它只是为sub2全局变量带来名称别名,并且
  6. Sub2被定义为sub.sub2
  7. 的子类
  8. __init__导入完成,Python继续执行步骤(4)中的b.inheritors()方法; Python导入sub.sub3并恢复到base调用 (来自main,而非来自base.Base)。此时唯一的子类 sub2base.py - 已打印
  9. 导入 base完成sub.sub1后,Python继续执行bodu Sub1 - 类base.Base被定义为__main__.base.__init__
  10. 的子类
  11. Python恢复sub.sub3执行,导入 sub.sub2 - 但它已经运行,__main__.Base.inheritors
  12. 也是如此 在__main__中调用
  13. sys.path.append,并打印否 的子类。
  14. 这是复杂历史的终结。

    你应该做什么

    首先:如果您需要执行proj欺骗,那么您的软件包就会出现问题。如果您希望运行(并动态导入其他模块),请将您的包设为proj.__init__,并指向base以导入cls.__subclasses__ - 但停止使用sys.path查找内容在你自己的包中。

    第二: cls调用没什么用处,因为它只会告诉你__init_subclass__的中间子类 - 如果有一个宏大的chid子类它会变成unoticed,

    最常见的模式是拥有Base的子类的注册表 - 在创建它们时,只需将新类添加到此记录中。这可以用Python中的元类来完成。 3.6,或者使用Python 3.6及其上的GotFocus方法。