【问题标题】:Cannot Get Import to Work with Subpackages无法导入以使用子包
【发布时间】:2020-07-29 01:32:35
【问题描述】:

以下结构(在Python 3.7 中)不允许我在模块B 中导入类A

package:
    package:
        __init__.py
        a:
            __init__.py
            a.py
        b:
            __init__.py
            b.py

顶级__init__.py 为空白。以下是剩余的文件:

一个

# package/package/a/__init__.py

from .a import A
# package/package/a/a.py

class A:

    def __init__(self):
        pass

b

# package/package/b/__init__.py

from .b import B
# package/package/b/b.py

from package.a.a import A

class B:

    def __init__(self):
        pass

不做任何其他事情,在 Windows 上,如果我尝试运行 b.py(在 b 文件夹中),我会收到以下错误:

ModuleNotFoundError: No module named 'package.a'

如果我在顶层添加main.py

package:
    package:
        __init__.py
        main.py
        a:
            __init__.py
            a.py
        b:
            __init__.py
            b.py

包含

# package/package/main.py

import a
import b

并运行main.py(在package/package 内),我得到了同样的错误。

如果我将b.py 更改为

# package/package/b/b.py

from ..a.a import A

class B:

    def __init__(self):
        pass

并运行b.py(从b 文件夹内)或main.py(从package/package 内),我得到的错误是

ValueError: attempted relative import beyond top-level package

python docs 看起来我应该能够做到这一点!

有人可以解释为什么我会收到这些错误吗?我看过几个类似的帖子,但他们没有解决我的问题:

  1. Importing Submodules Python 3
  2. Python submodule importing correctly in python 3.7 but not 3.6

【问题讨论】:

  • 你有一个循环导入。 test.b.b 导入test.a,然后导入test.a.a,然后导入test.b,后者导入test.b.b,但它正在被实例化,因此触发了ImportError,它冒泡到from test.a import A 语句和因此据报道ModuleNotFoundErrortest.a。具体来说,这里的导入将访问未完全加载的成员,最终导致此ImportError 参见:this thread 及其链接线程以获取更多信息
  • @metatoaster 我在a.py 中删除了from test.b import B,但仍然出现同样的错误。
  • 您在哪里运行b.py?如果这只是一个随机的目录集合(即不是一个“正确的”Python 包),即使您在 Test 目录中,它也会失败。但是,您可以通过运行PYTHONPATH=. python test/b/b.py 临时解决此问题,但这不是可移植的 - 理想情况下,您需要通过包含setup.py 将您的目录结构转换为一个包,然后将其安装到您的 Python 环境中,否则导入可能根本不会工作,因为 Python 不知道如何导入它们。
  • @metatoaster 我自己在目录b 中运行b.py。我这样做是因为我仍在编写我的代码并且还没有准备好发布。出于开发目的,最佳实践是使用pipenv 创建一个虚拟环境,然后将setup.py,然后pipenv install 写入该虚拟环境?一般来说,是否总是需要setuptools 才能使其正常工作?
  • 将您的项目设置为一个可以安装到不同 Python 环境中的包始终是最佳实践,这样可能更容易跨多个环境进行测试。 setuptools 使这项任务变得更加容易,但并非绝对必要 - 您始终可以选择手动创建 Python 所需的元数据文件,以将项目目录视为 Python 包(例如虚拟环境中的 .egg-link 文件site-packages 用于开发包,还有许多其他包,如 PKGINFO),但不建议使用此手动过程。

标签: python module package


【解决方案1】:

Python 运行的任何模块都称为顶级

  • 在您的 shell 中,当您运行 > py main.py(Linux 上为 $ python3 main.py)时,文件 main.py 是顶级的,称为顶级模块
  • 在解释器中,解释器本身总是顶层的,称为顶层环境(证明,在解释器中输入>>> __name__,它会返回'__main__'

不幸的是 (IMO),“顶级”一词在 python docs 中没有明确定义,因为它在几个不同的上下文中使用。无论如何,了解 Python 总是将顶级实体的 __name__ 重命名为 '__main__' 很重要。

PEP 328(在this SO post中解释)状态

相对导入使用模块的__name__ 属性来确定其在包层次结构中的位置。

由于 Python 将顶层模块的 __name__ 重命名为 '__main__'顶层模块没有包信息,因为它的名称中没有点。

因此,顶级模块不被视为任何包的一部分(即使它们很可能是!)。换句话说,对于从当前目录导入的包,'__main__' 确定什么是顶级。与'__main__'(在我的示例中为ab)处于同一级别的包是顶级包。

令人困惑的是,python docsPEP 328 给出了一个误导性的例子。相对导入显示的“正确用法”仅在特定上下文中有效

回想一下import 搜索sys.path 中列出的路径以查找要导入的包和模块。当前目录位于sys.path,以及内置包(如mathos)和已安装包(即pip installed 包)的路径。 Python 不会重命名非顶级包的__name__

因此,python docsPEP 328 示例仅适用于不在顶级目录中的包和模块

我应该写的是from a.a import A:

# package/package/b/b.py

from a.a import A

class B:

    def __init__(self):
        pass

由于package 位于顶级模块之上,因此尝试执行绝对导入(如from package.a.a import a)会导致ImportError,即使main.py 在包package 内。

话虽如此,如果你去 PyPI 和 GitHub 查看已发布的包,你会发现它们具有绝对导入,例如 import package.a.a!事实上,如果我要发布我的包并将导入保留为from a.a import A,最终用户将获得ImportError,因为他们安装了包package,但没有包a!此外,在当前配置中,我无法使用unittestpytest 测试用户可以按预期导入和使用我的包,因为我自己无法做到from package.a.a import A

那么问题就变成了如何编写和测试自己的自定义包?

诀窍在于这些包是在开发模式下编写的。要在开发模式下安装您的包,请从顶级目录运行 > pip install -e .(假设您已写入 setup.py;请参阅下面的注意)。

完成此操作后,python 会将您的包视为典型的库包(即 pip installed 包),因此 python 不会将其 __name__ 更改为 __main__。因此,您可以

  • 以绝对导入方式导入
  • 像最终用户一样测试和使用您的软件包,并且
  • 您对其所做的任何编辑都会在运行时立即生效,而无需您重新pip install 它,就像顶级目录中的软件包一样

开发软件包与独立程序之间的这一关键区别对于大多数初次开发人员(包括我自己)来说是一个巨大的困惑和挫败感,记住这一点非常重要。我希望这个答案能为其他人提供澄清,并可能在将来添加到文档中。如果这对您有帮助,请在下面的 cmets 中告诉我。

注意: pip install -e .,其中-e 代表“可编辑”,将链接(*.pth 文件)放在 python 安装文件夹中,以便将包视为已安装包,而且您在其中写入的任何更改都将立即生效(请参阅Python Packaging Tutorial)。因此,您可以使用它来开发自己的包或根据需要安装和编辑第三方包。这需要您创建一个setup.py,但是您的所有测试代码、客户端代码等都将能够以通常的方式导入您的包(即您可以像对待任何其他pip installed 包一样对待您的包)。通过配置pyproject.toml 文件,您可以使用poetryflit 实现相同的效果。

以下是一些其他有用的参考资料:

  1. realpython.com: Python Modules and Packages - An Introduction
  2. realpython.com: Python import: Advanced Techniques and Tips
  3. realpython.com: Absolute vs Relative Imports in Python

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-13
    • 2018-10-14
    • 2020-07-18
    • 2023-03-07
    • 1970-01-01
    相关资源
    最近更新 更多