【问题标题】:Imports inside python packages在 python 包中导入
【发布时间】:2018-09-11 20:03:23
【问题描述】:

我在以下代码中遇到了导入错误(ImportError: cannot import name '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

【问题讨论】:

  • 更改了类名以使代码更清晰

标签: python


【解决方案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.py中成功找到test_pkg.b

然后,在该模块中找到test_pkg.b.B,并将其导入到您的模块中,因为b.py 中当然有class B: 语句。


对于您的后续问题:

跨模块依赖是正确的方法吗?

跨模块依赖关系没有任何问题,只要它们不是循环的,而你的不是。

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

【讨论】:

  • 嗨,谢谢你的回答,我不明白你回答的第一部分到底是什么意思。代码的行为并不指向您的解释,因为在 main.py 中导入工作正常,但在包含重新导入后被破坏。我会将我对问题的看法添加到问题中
【解决方案2】:

我想写的代码大概是:

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中的使用。

【讨论】:

  • 我已经稍微更新了问题,以准确指出我在问什么。感谢您回答“其他”问题。
猜你喜欢
  • 2012-02-21
  • 2020-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-01
相关资源
最近更新 更多