ChatGPT解决这个技术问题 Extra ChatGPT

即使使用 __init__.py,如何修复“尝试在非包中进行相对导入”

我正在尝试使用以下目录结构来关注 PEP 328

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

core_test.py 我有以下导入语句

from ..components.core import GameLoopEvents

但是,当我运行时,我收到以下错误:

tests$ python core_test.py 
Traceback (most recent call last):
  File "core_test.py", line 3, in <module>
    from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package

环顾四周,我找到了“relative path not working even with __init__.py”和“Import a module from a relative path”,但它们没有帮助。

我在这里有什么遗漏吗?

我也对构建 unittest 项目的各种方式感到非常困惑,因此我编写了这个 fairly exhaustive sample project,其中涵盖了模块的深度嵌套、相对和绝对导入(有效和无效)以及来自的相对和绝对引用在包内,以及类的单、双和包级导入。为我立即帮我清理了事情!
我无法让您的测试正常工作。当我运行它们时,不断得到 no module named myimports.foo
@Blairg23 例如,我猜测预期的调用是 cd 进入 PyImports,然后运行 python -m unittest tests.test_abs
我同意吉恩的观点。我希望有一种调试导入过程的机制更有帮助。就我而言,我在同一个目录中有两个文件。我正在尝试将一个文件导入另一个文件。如果我在该目录中有一个 init.py 文件,则会收到 ValueError: Attempted relative import in non-package 错误。如果我删除 init.py 文件,则会收到错误 no module named 'NAME' 错误。
就我而言,我在同一个目录中有两个文件。我正在尝试将一个文件导入另一个文件。如果我在该目录中有一个 init.py 文件,则会收到 ValueError: Attempted relative import in non-package 错误。如果我删除 init.py 文件,则会收到错误 no module named 'NAME' 错误。真正令人沮丧的是,我有这个工作,然后我通过删除 .bashrc 文件,将 PYTHONPATH 设置为某个东西,然后我把自己开枪打死了,现在它不工作了。

J
John Strood

要详细说明 Ignacio Vazquez-Abrams's 答案:

Python 导入机制相对于当前文件的 __name__ 起作用。当您直接执行文件时,它没有通常的名称,而是使用 "__main__" 作为其名称。所以相对进口不起作用。

您可以按照 Igancio 的建议使用 -m 选项执行它。如果您的包的一部分打算作为脚本运行,您还可以使用 __package__ 属性来告诉该文件它应该在包层次结构中具有什么名称。

有关详细信息,请参阅 http://www.python.org/dev/peps/pep-0366/


我花了一段时间才意识到您不能从 tests 子目录中运行 python -m core_test - 它必须来自父目录,或者您必须将父目录添加到路径中。
@DannyStaple:不完全是。您可以使用 __package__ 确保可执行脚本文件可以相对导入同一包中的其他模块。没有办法从“整个系统”相对导入。我什至不确定你为什么要这样做。
我的意思是如果 __package__ 符号设置为“parent.child”,那么您就可以导入“parent.other_child”。也许我没有说得那么好。
@DannyStaple:好吧,链接文档中描述了它的工作原理。如果您在包 pack.subpack 中有一个脚本 script.py,那么将它的 __package__ 设置为 pack.subpack 将允许您执行 from ..module import somethingpack.module 导入某些内容。请注意,正如文档所述,您仍然必须在系统路径上拥有顶级包。这已经是导入模块的工作方式。 __package__ 所做的唯一一件事就是让您也可以将该行为用于直接执行的脚本。
我在直接执行的脚本中使用 __package__ 但不幸的是,我收到以下错误:“未加载父模块 'xxx',无法执行相对导入”
I
Ignacio Vazquez-Abrams

是的。你没有把它当作一个包来使用。

python -m pkg.tests.core_test

一个陷阱:请注意,最后没有“.py”!
我不是投票者中的任何一个,但考虑到这个问题和答案的受欢迎程度,我觉得这可以使用相当多更多细节。请注意诸如从哪个目录执行上述 shell 命令、您需要一直向下执行 __init__.py 以及允许这些可执行脚本导入所需的 __package__ 修改技巧(下面由 BrenBarn 描述)等内容(例如,当使用 shebang 并在 Unix shell 上执行 ./my_script.py 时)都会很有用。整个问题对我来说是非常棘手的,要弄清楚或找到简明易懂的文档。
注意:从 CLI 调用此行时,您需要位于目录 pkg 之外。然后,它应该按预期工作。如果您在 pkg 内并调用 python -m tests.core_test,它将不起作用。至少它不适合我。
说真的,你能解释一下你的答案是怎么回事吗?
@MarkAmery 几乎失去了我的想法,试图了解这一切是如何工作的,项目中的相对导入具有 py 文件的子目录,这些文件具有 __init__.py 文件,但您不断收到 ValueError: Attempted relative import in non-package 错误。我会为某个地方的某个人支付非常高的费用,最终用简单的英语解释所有这些是如何工作的。
P
Paolo Rovelli

这取决于您要如何启动脚本。

如果您想以经典方式launch your UnitTest from the command line,那就是:

python tests/core_test.py

然后,由于在这种情况下 'components''tests' 是同级文件夹,您可以使用 insertsys.path 模块的 append 方法。就像是:

import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents

否则,您可以 launch your script with the '-m' argument(请注意,在这种情况下,我们谈论的是一个包,因此您不能提供 '.py' 扩展名),即:

python -m pkg.tests.core_test

在这种情况下,您可以像以前一样简单地使用相对导入:

from ..components.core import GameLoopEvents

您最终可以混合使用这两种方法,以便您的脚本无论如何调用都能正常工作。例如:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        from components.core import GameLoopEvents
    else:
        from ..components.core import GameLoopEvents

如果我想使用 pdb 进行调试,我该怎么办?因为您使用 python -m pdb myscript.py 启动调试会话。
@dannynjust——这是一个很好的问题,因为你不能有 2 个主要模块。通常在调试时,我更喜欢在要开始调试的第一个点手动进入调试器。您可以通过将 import pdb; pdb.set_trace() 插入代码(内联)来实现。
使用 insert 而不是 append 更好吗?也就是说,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
使用 insert 更适合相对导入语义,其中本地包名称优先于已安装的包。特别是对于测试,您通常希望测试本地版本,而不是已安装的版本(除非您的测试基础设施安装了被测代码,在这种情况下,不需要相对导入,您不会遇到这个问题)。
您还应该提到,当您作为模块运行时,您不能位于包含 core_test 的目录中(这太容易了)
M
Miles Erickson

如果将当前目录附加到 sys.path,则可以直接使用 import components.core

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 这也可以
from os import sys 看起来像作弊 :)
@Piotr:它可能被认为更好,因为它稍微更清楚地显示了附加到 sys.path 的内容 - 当前文件所在目录的父级。
@flyingsheep:同意,我只使用普通的 import sys, os.path as path
仅供参考,为了在 ipython 笔记本中使用它,我将这个答案改编为:import os; os.sys.path.append(os.path.dirname(os.path.abspath('.')))。然后直接 import components.core 为我工作,根据需要从笔记本的父目录导入。
A
Allan Mwesigwa

在 core_test.py 中,执行以下操作:

import sys
sys.path.append('../components')
from core import GameLoopEvents

d
deepak

如果您的用例用于运行测试,并且确实如此,那么您可以执行以下操作。不要将测试脚本作为 python core_test.py 运行,而是使用 pytest 等测试框架。然后可以在命令行输入

$$ py.test

这将在您的目录中运行测试。这解决了 @BrenBarn 指出的 __name____main__ 的问题。接下来,将一个空的 __init__.py 文件放入您的测试目录,这将使测试目录成为您包的一部分。然后你就可以做

from ..components.core import GameLoopEvents

但是,如果您将测试脚本作为主程序运行,那么事情将再次失败。所以只需使用测试运行器。也许这也适用于其他测试运行程序,例如 nosetests,但我还没有检查过。希望这可以帮助。


M
Mohideen bin Mohammed

问题在于您的测试方法,

你试过python core_test.py

那么你会得到这个错误 ValueError: Attempted relative import in non-package

原因:您正在从非包源测试您的包。

所以从包源测试你的模块。

如果这是您的项目结构,

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

光盘包装

python -m tests.core_test # dont use .py

或从外部 pkg/

python -m pkg.tests.core_test

如果您想从同一目录中的文件夹导入单个 . 。每后退一步再加一个。

hi/
  hello.py
how.py

how.py

from .hi import hello

如果你想从 hello.py 导入如何

from .. import how

在示例 from .. import how 中,您如何从“how”文件中导入特定的类/方法。当我执行相当于 from ..how import foo 的操作时,我会得到“尝试相对导入超出顶级包”
@JamesHulse from .. import how 是否有效,但第二个语句无效?如果包含 hi 文件夹的文件夹不包含 _ _ init _ _.py 文件,我会假设这两个语句都不起作用。换句话说,如果 hi 文件夹中只有一个 init 文件,那么 hi 文件夹是最顶层的包,你不能超出它来寻址。
v
v4gil

我的快速修复是将目录添加到路径:

import sys
sys.path.insert(0, '../components/')

您的方法在所有情况下都不起作用,因为“../”部分是从您运行脚本的目录(core_test.py)中解析的。使用您的方法,您必须在运行 core_test.py 脚本之前 cd 到“测试”。
z
zhengcao

正如 Paolo 所说,我们有 2 种调用方法:

1) python -m tests.core_test
2) python tests/core_test.py

它们之间的一个区别是 sys.path[0] 字符串。由于 the interpret will search sys.path when doing import,我们可以使用 tests/core_test.py

if __name__ == '__main__':
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from components import core
    <other stuff>

在此之后,我们可以使用其他方法运行 core_test.py:

cd tests
python core_test.py
python -m core_test
...

注意,py36 仅测试过。


J
John Difool

旧线程。我发现将 __all__= ['submodule', ...] 添加到 __init__.py 文件,然后在目标中使用 from <CURRENT_MODULE> import * 可以正常工作。


J
Jayhello

你可以使用from pkg.components.core import GameLoopEvents,比如我用的是pycharm,下面是我的项目结构图,我只是从根包导入,然后就可以了:

https://i.stack.imgur.com/O8dKL.png


这对我不起作用。您是否必须在配置中设置路径?
@MohammadMahjoub您需要他还必须完成这项工作的所有初始化文件...不要忘记cnn_scratch中的那个
R
Rick Graves

这种方法对我有用,并且比某些解决方案更简洁:

try:
  from ..components.core import GameLoopEvents
except ValueError:
  from components.core import GameLoopEvents

父目录在我的PYTHONPATH中,父目录和这个目录下有__init__.py个文件。

以上在 python 2 中始终有效,但 python 3 有时会遇到 ImportError 或 ModuleNotFoundError (后者是 python 3.6 中的新内容,是 ImportError 的子类),因此以下调整在 python 2 和 3 中都适用于我:

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents

V
Vaishnavi Bala

尝试这个

import components
from components import *

s
suriyanto

由于您已经将所有内容标记为模块,因此如果您作为 python 模块启动,则无需使用相对引用。

代替

from ..components.core import GameLoopEvents

简单地

from pkg.components.core import GameLoopEvents

当您从 pkg 的父级运行时,请使用以下命令

python -m pkg.tests.core_test

H
HappyWaters

如果有人正在寻找解决方法,我偶然发现了一个。这里有一点上下文。我想测试我在文件中的一种方法。当我从内部运行它时

if __name__ == "__main__":

它总是抱怨相对进口。我尝试应用上述解决方案,但未能奏效,因为有许多嵌套文件,每个文件都有多个导入。

这就是我所做的。我刚刚创建了一个启动器,一个可以导入必要方法并调用它们的外部程序。虽然,这不是一个很好的解决方案,但它确实有效。


S
SteveCalifornia

这是一种会激怒所有人但效果很好的方法。在测试运行中:

ln -s ../components components

然后像往常一样导入组件。


这是一颗被严重低估的宝石!我不明白为什么没有足够的赞成票来使这个答案接近顶部!
K
Kai Aeberli

对我来说只有这个工作:我必须明确地将 package 的值设置为父目录,并将父目录添加到 sys.path

from os import path
import sys
if __package__ is None:
    sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
    __package__= "myparent"

from .subdir import something # the . can now be resolved

我现在可以使用 python myscript.py 直接运行我的脚本。


m
mon

python

.py 不适用于相对导入

当您从命令行运行 __main__ 模块时,问题是 relative import 不起作用

python <main_module>.py

PEP 338中明确说明。

2.5b1 的发布显示了此 PEP 和 PEP 328 之间令人惊讶的(尽管回想起来很明显)交互 - 显式相对导入在主模块中不起作用。这是因为相对导入依赖于 __name__ 来确定当前模块在包层次结构中的位置。在主模块中,__name__ 的值始终为 '__main__',因此显式相对导入总是会失败(因为它们仅适用于包内的模块)。

原因

Python Bug Tracker Issue1510172:绝对/相对导入不起作用?

这个问题实际上并不是 -m 开关所独有的。问题是相对导入是基于 __name__ 的,并且在主模块中,__name__ 始终具有值 __main__。因此,相对导入目前无法从应用程序的主模块正常工作,因为主模块不知道它真正适合 Python 模块命名空间的位置(这对于通过-m 开关,但直接执行文件和交互式解释器完全不走运)。

要进一步了解,请参阅 Relative imports in Python 3 了解详细说明以及如何解决。


S
SANTI SANTOSH MAHAPATRA

这非常令人困惑,如果您使用的是 pycharm 之类的 IDE,那就更令人困惑了。什么对我有用: 1. 进行 pycharm 项目设置(如果你从 VE 或 python 目录运行 python) 2. 你定义的方式没有错。有时它适用于 from folder1.file1 导入类

如果它不起作用,请使用 import folder1.file1 3. 您的环境变量应在系统中正确提及或在命令行参数中提供。


R
Ronny

我遇到过类似的问题,作为一名软件工程师,我认为这里建议的一些解决方案并不理想。如果你想要相对导入,你不应该尝试/除了然后有时做绝对导入。此外,要运行程序,您不必更改 sys.path。

此外,该程序应该始终工作,独立于您当前的工作目录,也独立于您如何启动它。

因此,我创建了一个新的实验性导入库:ultraimport 无论您如何运行代码,它都允许基于文件系统的导入。

从最初的问题,您可以将 core_test.py 更改为类似

import ultraimport
GameLoopEvents = ultraimport('__dir__/../components/core.py', 'GameLoopEvents')
print(GameLoopEvents)

无论您如何运行测试,它总会找到它。

$ python -m tests.core_test
<class 'core.GameLoopEvents'>
 python ./tests/core_test.py 
<class 'core.GameLoopEvents'>

我还把这个例子放到了 git repo 的 examples folder 中。

由于图书馆是实验性的,我对反馈很感兴趣。它对我有用,但尚未经过广泛测试。


r
rosefun

由于您的代码中包含if __name__ == "__main__",它不会作为包导入,因此您最好使用sys.path.append()来解决问题。


我认为您的文件中包含 if __name__ == "__main__" 不会对与导入相关的任何内容产生影响。