在python包内导入

时间:2018-09-11 20:03:24

标签: python

我在以下代码中遇到导入错误(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个其他问题:

  1. 具有跨模块依赖性是正确的方法吗?
  2. 是否可以在软件包 init .py中导入模块?

我的分析

基于阅读,我认识到最可能的问题是,因为关闭

__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

2 个答案:

答案 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
  1. 正确的方法是具有跨模块的依赖关系,而不是循环的。您应该弄清楚项目的层次结构,并将依赖图布置在DAG(有向无环图)中。

  2. 您在包装__init__.py中放入的物品将可以通过包装访问。您也可以参考此question,以了解__all____init__.py的用法。

相关问题