我在以下代码中遇到导入错误(ImportError:无法导入名称“ ClassB”)
目录结构:
main.py
test_pkg/
__init__.py
a.py
b.py
main.py
:
from test_pkg import ClassA, ClassB
__init__.py
:
from .a import ClassA
from .b import ClassB
a.py
:
from test_pkg import ClassB
class ClassA:
pass
b.py
:
class ClassB:
pass
过去,我通过在a.py中的导入中添加全名来快速进行“实验”来修复它:
from test_pkg.b import ClassB
class ClassA:
pass
我已阅读有关进口机械的信息,
此名称将在导入搜索的各个阶段中使用,它可能是子模块(例如, foo.bar.baz。在这种情况下,Python首先尝试导入foo,然后导入foo.bar,最后导入foo.bar.baz。 link2doc
我期望它会再次失败,因为它将在导入test_pkg的过程中尝试导入test_pkg,但是它可以正常工作。我的问题是为什么?
还有2个其他问题:
我的分析
基于阅读,我认识到最可能的问题是,因为关闭
__init__.py:
from .a import ClassA
from .b import ClassB
ClassA和ClassB导入是test_pkg导入的一部分,但随后在a.py中命中了import语句:
a.py:
from test_pkg import ClassB
class ClassA:
pass
并且失败,因为发生了循环依赖。
但是使用以下命令导入时可以使用
:from test_pkg.b import ClassB
并且根据我的理解,这不应该,因为:
此名称将在导入搜索的各个阶段使用,并且 可能是子模块的虚线路径,例如foo.bar.baz。在这种情况下, Python首先尝试导入foo,然后导入foo.bar,最后 foo.bar.baz。如果任何中间进口都失败了, 引发ModuleNotFoundError。
所以我期望两次导入的行为相同。
看起来像具有完整路径的导入不会启动有问题的test_pkg导入过程
from test_pkg.b import ClassB
答案 0 :(得分:1)
您的文件名为b.py
,但您尝试的是import B
,而不是import b
。
取决于您的平台(有关详细信息,请参见PEP 235),此方法可能有效,也可能无效。如果不起作用,则症状将与您看到的完全一样:ImportError: cannot import name 'B'
。
此修复程序适用于from test_okg import b
。或者,如果要将模块命名为B
,请将文件重命名为B.py
。
这实际上与包无关(除了您收到的错误消息是cannot import name 'B'
而不是No module named 'B'
之外,因为在失败的from … import
语句中,Python无法确定是否您无法从包中导入模块,或从模块中导入某些全局名称)。
那么,为什么这样做有效?
from test_pkg.b import B
我期望它会再次失败,因为它将在导入test_pkg的过程中尝试导入test_pkg,但是它可以正常工作。我的问题是为什么?
首先,导入test_pkg
并不是问题;导入test_pkg.B
是。然后您通过导入test_pkg.b
解决了这个问题。
test_pkg.b
中成功找到 test_pkg/b.py
。
然后,在该模块中找到test_pkg.b.B
,并将其导入到您的模块中,因为class B:
中当然有一个b.py
语句。
关于您的后续问题:
具有跨模块依赖性是正确的方法吗?
跨模块依赖关系没有任何问题,只要它们不是循环的,而您的不是。
test_pkg.a
可以绝对导入test_pkg.b
,例如from test_pkg import b
,这是完全可以的。
但是,通常最好使用相对导入,例如from . import b
(除非您需要在Python 2.x和3.x上工作相同的双版本代码)。 PEP 328解释了为什么相对导入通常更适合于包内依赖(以及为什么只是“通常”而不是“总是”)的原因。
可以在软件包
__init__.py
中导入模块吗?
是的。实际上,这是一个非常普遍的习语,用于多种目的。
例如,请参见asyncio.__init__.py
,该指南从其每个子模块中导入所有公共出口,然后重新导出。子模块中有一些很少使用的名称,它们不是以_
开头,但没有包含在__all__
中,如果要使用这些名称,则需要显式导入子模块。但是,典型程序中可能需要的所有内容都包含在__all__
中,并由程序包重新导出,因此您可以编写例如asyncio.Lock
而不是asyncio.locks.Lock
。 / p>
答案 1 :(得分:0)
我希望编写的代码可能是:
main.py
:
from test_pkg import A, B
b.py
:
class B:
pass
a.py
:
from .b import B
class A:
pass
__init__.py
:
from .a import A
from .b import B
main.py
:
from test_pkg import A, B
正确的方法是具有跨模块的依赖关系,而不是循环的。您应该弄清楚项目的层次结构,并将依赖图布置在DAG(有向无环图)中。
您在包装__init__.py
中放入的物品将可以通过包装访问。您也可以参考此question,以了解__all__
中__init__.py
的用法。