1247

我来过这里:

还有很多我没有复制的 URL,一些在 SO 上,一些在其他网站上,当我认为我会很快找到解决方案时。

永远重复出现的问题是:如何解决这个“尝试在非包中进行相对导入”消息?

ImportError: attempted relative import with no known parent package

我在 pep-0328 上构建了该软件包的精确副本:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

导入是从控制台完成的。

我确实在相应的模块中创建了名为 spam 和 egg 的函数。自然,它没有奏效。答案显然在我列出的第 4 个 URL 中,但对我来说都是校友。我访问的其中一个 URL 上有这样的响应:

相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为'main'),那么无论模块实际位于文件系统上的什么位置,相对导入都会被解析为好像该模块是顶级模块一样。

上面的回复看起来很有希望,但对我来说都是象形文字。所以我的问题是,如何让 Python 不返回给我“尝试在非包中进行相对导入”?据说有一个涉及-m的答案。

有人可以告诉我为什么 Python 会给出该错误消息,“非包”是什么意思,为什么以及如何定义“包”,以及用幼儿园儿童容易理解的术语给出的准确答案

4

14 回答 14

1656

脚本与模块

这是一个解释。简短的版本是直接运行 Python 文件和从其他地方导入该文件之间存在很大差异。 仅仅知道文件在哪个目录并不能确定 Python 认为它在哪个包中。 另外,这取决于您如何将文件加载到 Python 中(通过运行或通过导入)。

有两种方法可以加载 Python 文件:作为顶级脚本或作为模块。python myfile.py如果您直接执行文件,例如通过在命令行上键入,文件将作为顶级脚本加载。import当在其他文件中遇到语句时,它会作为模块加载。一次只能有一个顶级脚本;顶层脚本是你运行的 Python 文件。

命名

当一个文件被加载时,它被赋予一个名称(存储在它的__name__属性中)。如果它是作为顶级脚本加载的,它的名字是__main__. 如果它作为一个模块加载,它的名称是文件名,前面是它所属的任何包/子包的名称,用点分隔。

因此,例如在您的示例中:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

如果您导入moduleX(注意:导入,而不是直接执行),它的名称将是package.subpackage1.moduleX. 如果您导入moduleA,它的名称将是package.moduleA. 但是,如果您直接从命令行运行 moduleX,它的名称将改为__main__,如果您直接从命令行运行moduleA,它的名称将是__main__. 当一个模块作为顶级脚本运行时,它会丢失其正常名称,而改为__main__.

不通过其包含的包访问模块

还有一个额外的问题:模块的名称取决于它是从它所在的目录“直接”导入还是通过包导入的。仅当您在目录中运行 Python 并尝试在同一目录(或其子目录)中导入文件时,这才会有所不同。例如,如果您在目录中启动 Python 解释器,package/subpackage1然后执行import moduleX,则名称moduleX将只是moduleX,而不是package.subpackage1.moduleX。这是因为 Python 在交互输入解释器时会将当前目录添加到其搜索路径中;如果它在当前目录中找到要导入的模块,它就不会知道该目录是包的一部分,包信息也不会成为模块名称的一部分。

一种特殊情况是,如果您以交互方式运行解释器(例如,只需键入python并即时开始输入 Python 代码)。在这种情况下,该交互式会话的名称是__main__

现在这是您的错误消息的关键:如果模块的名称没有点,则它不被认为是 package 的一部分。文件在磁盘上的实际位置并不重要。重要的是它的名字是什么,它的名字取决于你如何加载它。

现在看看您在问题中包含的报价:

相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为'main'),那么无论模块实际位于文件系统上的什么位置,相对导入都会被解析为模块是顶级模块。

相对进口...

相对导入使用模块的名称来确定它在包中的位置。当您使用类似的相对导入from .. import foo时,圆点表示在包层次结构中提升一些级别。例如,如果您当前模块的名称是package.subpackage1.moduleX,则..moduleA表示package.moduleA. 为了使 afrom .. import起作用,模块的名称必须至少与import语句中的点一样多。

...仅在包中是相对的

但是,如果您的模块名称是__main__,则它不会被视为在包中。它的名字没有点,因此你不能from .. import在里面使用语句。如果您尝试这样做,您将收到“非包中的相对导入”错误。

脚本无法导入相对

您可能所做的是尝试从命令行运行moduleX或类似操作。当你这样做时,它的名字被设置为__main__,这意味着它里面的相对导入将会失败,因为它的名字并没有表明它在一个包中。请注意,如果您从模块所在的同一目录运行 Python,然后尝试导入该模块,也会发生这种情况,因为如上所述,Python 会“过早”在当前目录中找到该模块而没有意识到它是包的一部分。

还要记住,当您运行交互式解释器时,该交互式会话的“名称”始终是__main__. 因此,您不能直接从交互式会话中进行相对导入。相对导入仅用于模块文件中。

两种解决方案:

  1. 如果您确实想moduleX直接运行,但仍希望将其视为包的一部分,则可以执行python -m package.subpackage1.moduleX. 告诉 Python将-m其作为模块加载,而不是作为顶级脚本加载。

  2. 或者,也许您实际上并不想运行 moduleX,您只想运行一些其他脚本,例如myfile.py使用内部函数moduleX。如果是这种情况,请将其放在myfile.py 其他位置(不在package目录中)并运行它。如果myfile.py你在里面做类似的事情from package.moduleA import spam,它会工作得很好。

笔记

  • 对于这些解决方案中的任何一个,包目录(package在您的示例中)必须可以从 Python 模块搜索路径 ( sys.path) 访问。如果不是,您将根本无法可靠地使用包中的任何内容。

  • 从 Python 2.6 开始,用于包解析目的的模块“名称”不仅取决于其__name__属性,还取决于__package__属性。这就是为什么我避免使用显式符号__name__来引用模块的“名称”。由于 Python 2.6 模块的“名称”是有效的__package__ + '.' + __name__,或者只是__name__如果__package__None。)

于 2013-01-03T04:06:37.593 回答
70

这确实是python中的一个问题。混淆的根源是人们错误地将相对导入作为路径相对而不是相对路径。

例如,当您在faa.py中编写时:

from .. import foo

仅当faa.py在执行期间被python识别并加载为包的一部分时,这才有意义。在这种情况下,faa.py 的模块名称将是 例如some_packagename.faa。如果文件仅仅因为它在当前目录中而被加载,那么当 python 运行时,它的名称将不会引用任何包,最终相对导入将失败。

在当前目录中引用模块的一个简单解决方案是使用:

if __package__ is None or __package__ == '':
    # uses current directory visibility
    import foo
else:
    # uses current package visibility
    from . import foo
于 2018-03-25T19:50:43.227 回答
12

因此,在与许多其他人一起讨论了这个问题之后,我发现了Dorian B本文中发布的一个注释,它解决了我在开发用于 Web 服务的模块和类时遇到的特定问题,但我也想成为能够在我编码时使用 PyCharm 中的调试器工具对它们进行测试。要在自包含类中运行测试,我会在类文件的末尾包含以下内容:

if __name__ == '__main__':
   # run test code here...

但是如果我想在同一个文件夹中导入其他类或模块,我将不得不将我的所有导入语句从相对符号更改为本地引用(即删除点 (.))但是在阅读了 Dorian 的建议后,我尝试了他的'单线',它的工作!我现在可以在 PyCharm 中进行测试,当我在另一个被测类中使用该类时,或者在我的 Web 服务中使用它时,我可以保留我的测试代码!

# import any site-lib modules first, then...
import sys
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __name__ == '__main__' or parent_module.__name__ == '__main__':
    from codex import Codex # these are in same folder as module under test!
    from dblogger import DbLogger
else:
    from .codex import Codex
    from .dblogger import DbLogger

if 语句检查我们是否将此模块作为main运行,或者它是否正在另一个正在作为main测试的模块中使用。也许这很明显,但我在此处提供此注释,以防其他人对上述相关导入问题感到沮丧,可以使用它。

于 2018-10-05T01:08:26.917 回答
10

这是一个通用配方,经过修改以适合作为示例,我现在正在使用它来处理作为包编写的 Python 库,其中包含相互依赖的文件,我希望能够在其中零碎地测试它们的一部分。让我们称之为它lib.foo并说它需要访问lib.fileAfor 函数f1f2, 和lib.fileBClass3

我已经包括了一些print电话来帮助说明这是如何工作的。在实践中,您可能希望删除它们(也许还有这from __future__ import print_function条线)。

这个特殊的例子太简单了,以至于我们真的需要在sys.path. (在我们确实需要它的情况下,请参阅Lars 的回答,当我们有两个或更多级别的包目录时,然后我们使用- 但在这里也没有真正的伤害。)在没有测试。但是,如果每个导入的文件都插入相同的路径——例如,如果两者都想从包中导入实用程序——这会多次使用相同的路径,所以在样板文件中包含 是很好的。os.path.dirname(os.path.dirname(__file__))if _i in sys.pathfileAfileBsys.pathif _i not in sys.path

from __future__ import print_function # only when showing how this works

if __package__:
    print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__))
    from .fileA import f1, f2
    from .fileB import Class3
else:
    print('Not a package; __name__ is {!r}'.format(__name__))
    # these next steps should be used only with care and if needed
    # (remove the sys.path manipulation for simple cases!)
    import os, sys
    _i = os.path.dirname(os.path.abspath(__file__))
    if _i not in sys.path:
        print('inserting {!r} into sys.path'.format(_i))
        sys.path.insert(0, _i)
    else:
        print('{!r} is already in sys.path'.format(_i))
    del _i # clean up global name space

    from fileA import f1, f2
    from fileB import Class3

... all the code as usual ...

if __name__ == '__main__':
    import doctest, sys
    ret = doctest.testmod()
    sys.exit(0 if ret.failed == 0 else 1)

这里的想法是这样的(请注意,这些在 python2.7 和 python 3.x 中都具有相同的功能):

  1. 如果作为import libfrom lib import foo作为从普通代码导入的常规包运行,__package则 islib__name__is lib.foo。我们采用第一个代码路径,导入 from.fileA等。

  2. 如果运行为python lib/foo.py__package__将是 None 并且__name__将是__main__

    我们采用第二条代码路径。该lib目录已经存在,sys.path因此无需添加它。fileA我们从等进口。

  3. 如果在lib目录中运行 as python foo.py,则行为与案例 2 相同。

  4. 如果在lib目录中运行 as python -m foo,行为类似于情况 2 和 3。但是lib目录的路径不在 中sys.path,所以我们在导入之前添加它。如果我们运行 Python 然后import foo.

    (由于. in sys.path,我们实际上不需要在此处添加绝对版本的路径。这是我们想要做的更深的包嵌套结构产生影响的地方from ..otherlib.fileC import ...。如果您不这样做,您可以完全省略所有sys.path操作。)

笔记

仍然有一个怪癖。如果你从外面运行这整个事情:

$ python2 lib.foo

或者:

$ python3 lib.foo

行为取决于lib/__init__.py. 如果存在且为空,则一切正常:

Package named 'lib'; __name__ is '__main__'

但是,如果lib/__init__.py 它自己导入routine以便它可以routine.name直接导出为lib.name,你会得到:

$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'

也就是说,模块会被导入两次,一次通过包导入,然后再次导入,__main__以便它运行您的main代码。Python 3.6 及更高版本对此发出警告:

$ python3 lib.routine
Package named 'lib'; __name__ is 'lib.foo'
[...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
after import of package 'lib', but prior to execution of 'lib.foo';
this may result in unpredictable behaviour
  warn(RuntimeWarning(msg))
Package named 'lib'; __name__ is '__main__'

警告是新的,但警告的行为不是。这是一些人所说的双重进口陷阱的一部分。(有关更多详细信息,请参阅issue 27487。) Nick Coghlan 说:

下一个陷阱存在于所有当前版本的 Python 中,包括 3.3,并且可以总结为以下一般准则:“永远不要将包目录或包内的任何目录直接添加到 Python 路径”。

请注意,虽然我们在此处违反了该规则,但当正在加载的文件作为包的一部分加载时才这样做,并且我们的修改是专门为允许我们访问该包中的其他文件而设计的。(而且,正如我所指出的,我们可能根本不应该对单级包这样做。)如果我们想要更加干净,我们可以将其重写为,例如:

    import os, sys
    _i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    if _i not in sys.path:
        sys.path.insert(0, _i)
    else:
        _i = None

    from sub.fileA import f1, f2
    from sub.fileB import Class3

    if _i:
        sys.path.remove(_i)
    del _i

也就是说,我们修改sys.path了足够长的时间以实现我们的导入,然后将其恢复原状(_i当且仅当我们添加了 的一份副本时才删除一份_i)。

于 2017-05-06T02:08:15.153 回答
5

这是我不推荐的一种解决方案,但在某些根本没有生成模块的情况下可能很有用:

import os
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(parent_dir_name + "/your_dir")
import your_script
your_script.a_function()
于 2016-06-21T02:06:19.723 回答
5

一门外语有太多太长的答案。所以我会尽量缩短。

如果你写from . import module的,与你想的相反,module不会从当前目录导入,而是从你的包的顶层导入!如果您将 .py 文件作为脚本运行,它根本不知道顶层在哪里,因此拒绝工作。

py -m package.module如果你从上面的目录这样启动它package,那么 python 就知道顶层在哪里。这与java非常相似:java -cp bin_directory package.class


下一个问题:如何从当前级别导入?

于 2021-01-06T03:33:12.557 回答
3

@BrenBarn 的回答说明了一切,但如果你像我一样,可能需要一段时间才能理解。这是我的案例以及@BrenBarn 的答案如何适用于它,也许它会对您有所帮助。

案子

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

使用我们熟悉的示例,并添加 moduleX.py 对 ..moduleA 的相对导入。鉴于我尝试在导入 moduleX 的 subpackage1 目录中编写测试脚本,但随后得到了 OP 描述的可怕错误。

解决方案

将测试脚本移动到与包相同的级别并导入 package.subpackage1.moduleX

解释

如前所述,相对导入是相对于当前名称进行的。当我的测试脚本从同一目录导入 moduleX 时,moduleX 中的模块名称为 moduleX。当遇到相对导入时,解释器无法备份包层次结构,因为它已经在顶部

当我从上面导入moduleX时,moduleX里面的名字是package.subpackage1.moduleX,可以找到相对导入

于 2019-08-08T23:18:56.890 回答
1

__name__取决于相关代码是在全局命名空间中运行还是作为导入模块的一部分运行。

如果代码不在全局空间中运行,__name__将是模块的名称。如果它在全局命名空间中运行——例如,如果你在控制台中输入它,或者使用python.exe yourscriptnamehere.pythen将模块作为脚本运行,那么__name__就变成"__main__".

你会看到很多 python 代码 if __name__ == '__main__'用于测试代码是否从全局命名空间运行——这允许你拥有一个兼作脚本的模块。

您是否尝试从控制台进行这些导入?

于 2013-01-03T04:01:16.457 回答
1

我有一个类似的问题,我不想更改 Python 模块搜索路径,并且需要从脚本相对加载模块(尽管BrenBarn 在上面很好地解释了“脚本无法导入相对于所有内容” )。

所以我使用了以下技巧。不幸的是,它依赖于imp自 3.4 版以来被弃用的模块,以支持importlib. (这也可能importlib吗?我不知道。)不过,黑客现在仍然有效。

从位于文件夹中的脚本访问moduleXin成员的示例:subpackage1subpackage2

#!/usr/bin/env python3

import inspect
import imp
import os

def get_script_dir(follow_symlinks=True):
    """
    Return directory of code defining this very function.
    Should work from a module as well as from a script.
    """
    script_path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        script_path = os.path.realpath(script_path)
    return os.path.dirname(script_path)

# loading the module (hack, relying on deprecated imp-module)
PARENT_PATH = os.path.dirname(get_script_dir())
(x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1'])
module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc)

# importing a function and a value
function = module_x.my_function
VALUE = module_x.MY_CONST

一种更简洁的方法似乎是修改 Federico 提到的用于加载模块的 sys.path。

#!/usr/bin/env python3

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    # __file__ should be defined in this case
    PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
   sys.path.append(PARENT_DIR)
from subpackage1.moduleX import *
于 2016-07-19T10:28:28.483 回答
0

相对导入使用模块的名称属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它被设置为'main'),那么无论模块实际位于文件系统上的什么位置,相对导入都会被解析为好像该模块是顶级模块一样。

向 PyPi 写了一个小 Python 包,可能会帮助这个问题的观众。如果希望能够从包/项目中运行包含包含上层包的导入的 python 文件,而不直接在导入文件的目录中,则该包充当解决方法。https://pypi.org/project/import-anywhere/

于 2018-02-24T16:11:58.920 回答
0

我的 SO 答案添加到相同的问题,因为对于包开发人员需要如何构建其导入语句与最终用户之间存在一个关键区别。

于 2022-02-26T02:25:46.650 回答
-1

另一个肮脏但有效的解决方法。假设您处于包裹的顶层。

import sys
from os.path import dirname, basename

if __package__ is None:
    sys.path.insert(0, '..')
    __package__ = basename(dirname(sys.argv[0]))

from . import your_module

与此处的另一个答案相比,优势在于您不需要更改由 IDE 自动生成的导入。

编辑:注意!陷阱!!

  • 如果您使用sys.path.append('..')而不是sys.path.insert(0, '..'),这将在此文件结构上失败。在这种情况下,your_module.py将被导入而不是your_module.
    your_module
      your_module.py
  • 需要__init__.py你的目录中。
于 2020-11-21T00:32:50.087 回答
-2

为了让 Python 不返回给我“尝试在非包中进行相对导入”。

package/
   __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

仅当您对父文件应用相对导入时,才会出现此错误。例如__main__,在您编码后 print(__name__)父文件已经返回moduleA.py

所以这个文件已经__main__不能再返回任何父包了。

包文件中需要相对导入,subpackage1并且subpackage2

您可以使用..来引用父目录或模块。但是,如果父级已经是顶级包,则它不能进一步超出该父目录(包)。您对父级应用相对导入的此类文件只能与绝对导入应用一起使用。

如果您在父包中使用绝对导入,则不会出现错误,因为即使您的文件位于子包中,python 也知道谁在包的顶层,因为$PYTHONPATH它定义了项目的顶层。

于 2019-01-06T10:30:00.123 回答
-2

按照示例:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py
  • 在脚本顶部添加以下行。

    # For relative imports to work
    import sys
    
  • 现在根据您要从哪里导入模块,添加以下行并在每次导入之前修改点。在我们的示例中,我们moduleAmoduleX.

    sys.path.append("..")
    import moduleA
    

自从 OP 提出这个问题以来已经 8 年了,并且在这些年之后,Python 并没有在他们的更新中解决这个问题。

相对导入不起作用,因为在运行脚本时,它认为它的名字是__main__而不是filename.py

于 2021-07-15T12:11:15.303 回答