Python 继承检测是如何工作的?
How does Python inheritance detection work?
我有一个基础 class 和几个继承自它的子 class。我正在尝试动态检测哪些子 classes 动态地从基础 class 继承。我目前正在通过动态导入基础 class __init__()
中的所有子 classes,然后使用 __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'>]
上面的输出完全符合我的预期。当我 运行 base.py 使用 Python 命令行时变得很奇怪:
python3 base.py
[<class 'sub.sub2.Sub2'>]
[]
现在我想我明白在第二种情况下有两个打印,因为 Python 导入器最初在 sys.modules
全局变量中看不到 base.py
,所以当subclass 被导入,它将再次导入 base.py
并且代码将被第二次执行。这个解释没有解释为什么它第一次打印 [<class 'sub.sub2.Sub2'>]
而不是 [<class 'sub.sub1.Sub1'>]
因为 sub.sub1.Sub1
是首先导入的,它也没有解释为什么只有 sub.sub2.Sub2
出现在 __subclasses__()
而 sub.sub1.Sub1
没有。
任何能帮助我理解 Python 在这方面如何工作的解释都将不胜感激!
编辑:我想 运行 使用 python base.py
的模块,所以也许我可以为此指出正确的方向?
你结了一个结。
一个复杂的、不必要的结。我可以弄清楚 - 但我不知道我是否可以牢记它以清楚地解释发生了什么:-)
但首先要注意一件事:这与 "inheritance detection" 关系不大,而与导入系统有关 - 您将其打成一个复杂的结。
因此,您得到了意想不到的结果,因为当您执行 python base.py
时,base 的内容被记录为 sys.modules
中名为 __main__
的模块。
通常,Python 永远不会再次导入模块和 运行 相同的代码:在找到试图导入现有模块的导入语句时,它只是创建一个指向现有模块的新变量。如果该模块尚未完成其主体的执行,则并非所有 classes 或变量都将出现在第二个 import 语句所在的位置。调用 importlib 并没有更好的效果——它们只是没有自动执行可变出价部分。当您进行循环导入、更改导入路径并从另一个文件导入名为 base
的模块时,Python 不知道这是同一个 base
即 __main__
。因此,新的得到了一个新的导入,以及 sys.modules 中的第二个条目,如 base
。
如果你只是在你的 inheritors 方法中打印 __class__
,它会很清楚:
@classmethod
def inheritors(cls):
print("At class {}. Subclasses: {}".format(__class__, cls.__subclasses__()))
然后你会看到 "base.Base" 有 "sub2" subclass 而 __main__.Base
没有 subclasses.
现在,让我尝试为它设置时间表:
base.py
导入为 __main__
和 运行s 直到行 b =
Base()
。此时Base的__init__
方法会导入
子模块
- 子模块
sub1
是 运行,更改 sys.path,并且
重新导入 base.py 作为 base
模块。
- 的内容
基本模块是 运行 直到满足 base.Base 中的
__init__
方法;
其中导入sub.sub1
,Python发现这个模块有
已经导入并在 sys.modules
中。它的代码没有被
已完成,但 Sub1
基数尚未定义。
- 在 base 的 sub1 导入中,
__init__
尝试导入 sub.sub2
。那
是 Python 的新模块,因此它被导入
- 关于导入
sub2
,当满足import base
时,Python将模块识别为
已经导入(尽管不是所有的初始化代码
是完整的) - 它只是将名称别名带到 sub2 全局变量中,并且
继续
- Sub2 定义为
base.Base
的子 class
sub.sub2
导入完成,Python 恢复到第(4)步的 __init__
方法; Python 导入 sub.sub3 并恢复到 b.inheritors()
调用
(来自 base
,而不是来自 main
)。此时唯一的subclass
base.Base
是 sub2
- 即打印
- 正在导入
base.py
as base
完成,并且 Python 恢复执行 bodu
sub.sub1
- class Sub1
定义为 base.Base
的子 class
- Python 恢复
__main__.base.__init__
执行,导入
sub.sub2 - 但它已经是 运行,与 sub.sub3
相同
__main__.Base.inheritors
在 __main__
中调用,并打印 no
子classes.
一段复杂的历史就此结束。
你应该做什么
首先:如果您需要进行 sys.path.append
欺骗,那么您的包裹有问题。让你的包是 proj
,如果你希望它是 运行 并指向 proj.__init__
导入 base
(并动态导入其他模块) - 但停止摆弄 sys.path在你自己的包里找东西。
第二个:
cls.__subclasses__
调用没什么用,因为它只会告诉你 cls
的直接子 classes - 如果有一个 grand-chid subclass 它会不被注意,
最常见的模式是注册您的 Base 的子 classes - 在创建它们时,只需将新的 classes 添加到该记录中。这可以在 Python < 3.6 中使用 metaclass 来完成,或者在 Python 3.6 及以后使用 __init_subclass__
方法。
我有一个基础 class 和几个继承自它的子 class。我正在尝试动态检测哪些子 classes 动态地从基础 class 继承。我目前正在通过动态导入基础 class __init__()
中的所有子 classes,然后使用 __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'>]
上面的输出完全符合我的预期。当我 运行 base.py 使用 Python 命令行时变得很奇怪:
python3 base.py
[<class 'sub.sub2.Sub2'>]
[]
现在我想我明白在第二种情况下有两个打印,因为 Python 导入器最初在 sys.modules
全局变量中看不到 base.py
,所以当subclass 被导入,它将再次导入 base.py
并且代码将被第二次执行。这个解释没有解释为什么它第一次打印 [<class 'sub.sub2.Sub2'>]
而不是 [<class 'sub.sub1.Sub1'>]
因为 sub.sub1.Sub1
是首先导入的,它也没有解释为什么只有 sub.sub2.Sub2
出现在 __subclasses__()
而 sub.sub1.Sub1
没有。
任何能帮助我理解 Python 在这方面如何工作的解释都将不胜感激!
编辑:我想 运行 使用 python base.py
的模块,所以也许我可以为此指出正确的方向?
你结了一个结。 一个复杂的、不必要的结。我可以弄清楚 - 但我不知道我是否可以牢记它以清楚地解释发生了什么:-)
但首先要注意一件事:这与 "inheritance detection" 关系不大,而与导入系统有关 - 您将其打成一个复杂的结。
因此,您得到了意想不到的结果,因为当您执行 python base.py
时,base 的内容被记录为 sys.modules
中名为 __main__
的模块。
通常,Python 永远不会再次导入模块和 运行 相同的代码:在找到试图导入现有模块的导入语句时,它只是创建一个指向现有模块的新变量。如果该模块尚未完成其主体的执行,则并非所有 classes 或变量都将出现在第二个 import 语句所在的位置。调用 importlib 并没有更好的效果——它们只是没有自动执行可变出价部分。当您进行循环导入、更改导入路径并从另一个文件导入名为 base
的模块时,Python 不知道这是同一个 base
即 __main__
。因此,新的得到了一个新的导入,以及 sys.modules 中的第二个条目,如 base
。
如果你只是在你的 inheritors 方法中打印 __class__
,它会很清楚:
@classmethod
def inheritors(cls):
print("At class {}. Subclasses: {}".format(__class__, cls.__subclasses__()))
然后你会看到 "base.Base" 有 "sub2" subclass 而 __main__.Base
没有 subclasses.
现在,让我尝试为它设置时间表:
base.py
导入为__main__
和 运行s 直到行b = Base()
。此时Base的__init__
方法会导入 子模块- 子模块
sub1
是 运行,更改 sys.path,并且 重新导入 base.py 作为base
模块。 - 的内容
基本模块是 运行 直到满足 base.Base 中的
__init__
方法; 其中导入sub.sub1
,Python发现这个模块有 已经导入并在sys.modules
中。它的代码没有被 已完成,但Sub1
基数尚未定义。 - 在 base 的 sub1 导入中,
__init__
尝试导入sub.sub2
。那 是 Python 的新模块,因此它被导入 - 关于导入
sub2
,当满足import base
时,Python将模块识别为 已经导入(尽管不是所有的初始化代码 是完整的) - 它只是将名称别名带到 sub2 全局变量中,并且 继续 - Sub2 定义为
base.Base
的子 class
sub.sub2
导入完成,Python 恢复到第(4)步的__init__
方法; Python 导入 sub.sub3 并恢复到b.inheritors()
调用 (来自base
,而不是来自main
)。此时唯一的subclassbase.Base
是sub2
- 即打印- 正在导入
base.py
asbase
完成,并且 Python 恢复执行 bodusub.sub1
- classSub1
定义为base.Base
的子 class
- Python 恢复
__main__.base.__init__
执行,导入 sub.sub2 - 但它已经是 运行,与sub.sub3
相同
__main__.Base.inheritors
在__main__
中调用,并打印 no 子classes.
一段复杂的历史就此结束。
你应该做什么
首先:如果您需要进行 sys.path.append
欺骗,那么您的包裹有问题。让你的包是 proj
,如果你希望它是 运行 并指向 proj.__init__
导入 base
(并动态导入其他模块) - 但停止摆弄 sys.path在你自己的包里找东西。
第二个:
cls.__subclasses__
调用没什么用,因为它只会告诉你 cls
的直接子 classes - 如果有一个 grand-chid subclass 它会不被注意,
最常见的模式是注册您的 Base 的子 classes - 在创建它们时,只需将新的 classes 添加到该记录中。这可以在 Python < 3.6 中使用 metaclass 来完成,或者在 Python 3.6 及以后使用 __init_subclass__
方法。