在隔离了几个小时的错误之后,我提出了以下MCVE示例来演示我遇到的问题:
a.py:
from b import get_foo_indirectly
class Foo:
pass
if __name__ == '__main__':
print("Indirect:", isinstance(get_foo_indirectly(), Foo))
print("Direct:", isinstance(Foo(), Foo))
b.py:
def get_foo_indirectly():
from a import Foo
return Foo()
a.py的预期输出为:
Indirect: True
Direct: True
实际输出为:
Indirect: False
Direct: True
此外,如果我创建一个单独的模块c.py,输出将如预期的那样:
from a import Foo
from b import get_foo_indirectly
if __name__ == '__main__':
print("Indirect:", isinstance(get_foo_indirectly(), Foo))
print("Direct:", isinstance(Foo(), Foo))
很明显,isinstance
与导入机制之间的交互行为不像我预期的那样。循环进口的使用似乎使我很难受。为什么?这是Python的预期行为吗?
请注意,这是我遇到此行为的实际上下文的非常简化;模块a和b都是大模块,并且b被分离是因为它有别于a的目的。现在,我已经了解了循环导入的后果,我可能会合并它们,也许会降低b中某些长期存在的行为。
答案 0 :(得分:7)
运行Python脚本时,它将自动采用名称__main__
。在您将a.py
导入b.py
时,Python假定使用通常的模块名称(即文件名),并且在运行时Python更改为__main__
,因为它是入口点脚本;因此,就像Foo
类是在两个不同的位置声明的:__main__
模块和a
模块。
然后,您要比较a.Foo
(在get_foo_indirectly
内部创建)和__main__.Foo
的实例。
这已经在here中进行了讨论。
如果需要循环导入,请不要将入口点脚本放入循环中。这样,您可以避免这种非常令人困惑的Python行为。
答案 1 :(得分:4)
我运行了相同的脚本,发现可以添加一些行来显示一些有趣的差异:
from b import get_foo_indirectly
class Foo:
pass
if __name__ == '__main__':
print("Indirect:", isinstance(get_foo_indirectly(), Foo))
print(type(get_foo_indirectly()))
print("Direct:", isinstance(Foo(), Foo))
print(type(Foo()))
输出:
Indirect: False
<class 'a.Foo'>
Direct: True
<class '__main__.Foo'>
现在,对于您的c.py
示例,两者都是a.Foo
,因此它们的评估结果相同。似乎可以推断出,对象也被跟踪到它们来自的文件/模块路径。
这是一个重要的区别,其作用超出了__main__
的调用(将路径指定为__main__
,而不是来自PATH
,@ Gabriel,@ ehacinom的当前路径)。假设您在不同的文件中定义了完全相同的类,例如d.py
:
class Foo:
pass
然后您尝试将它们导入相同的类e.py
:
from a import Foo
from d import Foo as Fooo
print(type(Foo()))
print(type(Fooo()))
您将获得:
<class 'a.Foo'>
<class 'd.Foo'>
这是python名称空间类的方式。此外,如果将d.py
移到目录/d
中,目录中的__init__.py
,则该类将变为
<class 'd.d.Foo'>
所有路径都相对于python PATH
。 site_packages
中安装的模块将在PATH
上可用,并且type
将返回从基本目录开始的对象的路径,等等:
<class 'matplotlib.figure.Figure'>
答案 2 :(得分:2)
在__main__
中进行导入必须更改类的命名空间。
__name__
是一个内置变量,其值为当前模块的名称。您正在__name__ == __main__
文件末尾覆盖特殊变量a.py
,并在该上下文中导入Foo
。
如果您在Foo()
中打印a.py __main__()
,则会得到__main__.Foo instance
如果您在Foo()
中打印b.py get_foo_indirectly()
,则会得到a.Foo instance
由于循环导入,因此在b.py
中将Foo
导入到函数中,该函数在__main__
中被调用。如果在终端中定义一个类,也会发生类似的事情-它的名称空间为__console__
。