第十亿次相对进口

我一直在这里:

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

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

ImportError: attempted relative import with no known parent package

我在pep-0328上构建了一个包的精确副本:

package/__init__.pysubpackage1/__init__.pymoduleX.pymoduleY.pysubpackage2/__init__.pymoduleZ.pymoduleA.py

导入是从控制台完成的。

我确实在相应的模块中制作了名为spam和鸡蛋的函数。自然,它不起作用。答案显然在我列出的第四个URL中,但对我来说都是校友。我访问的一个URL上有这样的回应:

相对导入使用模块的name属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如设置为main),则相对导入将被解析为该模块是顶层模块,而不管该模块实际位于文件系统上的位置。

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

有人能告诉我为什么Python会给出错误消息,“非包”是什么意思,为什么以及如何定义“包”,以及精确的答案用简单易懂的术语让幼儿园的孩子理解

477154 次浏览

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

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

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

您是否尝试从控制台导入这些内容?

脚本vs.模块

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

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

命名

加载文件时,它会被赋予一个名称(存储在其__name__属性中)。

  • 如果它是作为顶级脚本加载的,则其名称为__main__
  • 如果它是作为模块加载的,它的名称是[文件名,前面是它所属的任何包/子包的名称,用点分隔],例如package.subpackage1.moduleX

但是请注意,如果您使用类似于python -m package.subpackage1.moduleX的东西从shell命令行加载moduleX作为模块,那么__name__仍然是__main__

例如,在您的示例中:

package/__init__.pysubpackage1/__init__.pymoduleX.pymoduleA.py

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

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

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

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

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

现在看看你在问题中包含的引用:

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

相对进口…

相对导入使用模块的姓名来确定它在包中的位置。当你使用像from .. import foo这样的相对导入时,点表示在包层次结构中提升一些级别。例如,如果你当前模块的名称是package.subpackage1.moduleX,那么..moduleA将意味着package.moduleAfrom .. import要起作用,模块名称必须至少与import语句中的点一样多。

…只是相对的

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

脚本无法导入相对

你可能做的是尝试从命令行运行moduleX之类的东西。当你这样做时,它的名称被设置为__main__,这意味着它内部的相对导入将失败,因为它的名称不会显示它在一个包中。请注意,如果你从模块所在的同一目录运行Python,然后尝试导入该模块,也会发生这种情况,因为如上所述,Python会“太早”在当前目录中找到模块,而没有意识到它是包的一部分。

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

两种解决方案:

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

  2. 或者您实际上并不想要运行moduleX,您只是想运行一些其他脚本,例如myfile.py使用moduleX中运行。如果是这种情况,请将myfile.py去别的地方-myfile.py0放在package目录中-并运行它。如果在myfile.py中您做类似于from package.moduleA import spam的事情,它会正常工作。

备注

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

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

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

import osimport sysparent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))sys.path.append(parent_dir_name + "/your_dir")import your_scriptyour_script.a_function()

我有一个类似的问题,我不想改变Python模块搜索路径和需要加载一个模块相对从脚本(尽管msgstr"脚本不能相对于所有文件导入" BrenBarn很好地解释了上面)。

所以我使用了以下黑客。不幸的是,它依赖于imp模块从3.4版本开始被弃用,转而支持importlib。(importlib也可以吗?我不知道。)尽管如此,黑客现在仍然有效。

从驻留在subpackage2文件夹中的脚本访问subpackage1moduleX的成员的示例:

#!/usr/bin/env python3
import inspectimport impimport 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 valuefunction = module_x.my_functionVALUE = 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 casePARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))sys.path.append(PARENT_DIR)from subpackage1.moduleX import *

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

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

这个特殊的例子太简单了,无法显示我们何时真的需要在sys.path中插入一个条目。(参见Lars的回答,当我们有两个或更多级别的包目录时,我们需要它,然后我们使用os.path.dirname(os.path.dirname(__file__)),但在这里也不是真正的伤害。)在没有if _i in sys.path测试的情况下这样做也足够安全。然而,如果每个导入的文件插入相同的路径——例如,如果fileAfileB都想从包中导入实用程序——这会使sys.path与相同的路径杂乱很多次,所以在样板中有if _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, f2from .fileB import Class3else: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, f2from fileB import Class3
... all the code as usual ...
if __name__ == '__main__':import doctest, sysret = doctest.testmod()sys.exit(0 if ret.failed == 0 else 1)

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

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

  2. 如果以python lib/foo.py运行,__package__将为无,__name__将为__main__

    我们采用第二个代码路径。lib目录已经在sys.path中,所以不需要添加它。我们从fileA导入,等等。

  3. 如果在lib目录中作为python foo.py运行,则行为与情况2相同。

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

    (由于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.fooPackage named 'lib'; __name__ is 'lib.foo'Package named 'lib'; __name__ is '__main__'

也就是说,该模块被导入两次,一次通过包,然后再次作为__main__,以便它运行你的main代码。Python 3.6及更高版本警告这一点:

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

警告是新的,但警告行为不是。它是一些人称之为双重进口陷阱的一部分。(更多细节见问题27487。)尼克·科格兰说:

下一个陷阱存在于所有当前版本的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, f2from sub.fileB import Class3
if _i:sys.path.remove(_i)del _i

也就是说,我们修改sys.path足够长的时间来实现我们的导入,然后将其放回原样(当且仅当我们添加_i的一个副本时删除_i的一个副本)。

相对导入使用模块的name属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如设置为main),则相对导入将被解析为该模块是顶层模块,而不管该模块实际位于文件系统上的位置。

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

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

例如,当您写入faa.py时:

from .. import foo

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

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

if __package__ is None or __package__ == '':# uses current directory visibilityimport fooelse:# uses current package visibilityfrom . import foo

所以在和其他许多人一起吹毛求疵之后,我在这个文章中遇到了Dorian b发布的一个注释,它解决了我遇到的特定问题,我将在其中开发用于Web服务的模块和类,但我也希望能够在编码时使用PyCharm中的调试器工具测试它们。

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

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

# import any site-lib modules first, then...import sysparent_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 DbLoggerelse:from .codex import Codexfrom .dblogger import DbLogger

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

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

这个案子

package/__init__.pysubpackage1/__init__.pymoduleX.pymoduleA.py

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

解决方案

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

补充说明

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

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

外语里有太多太长的问题,所以我尽量把它缩短。

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

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

在大多数情况下,当我看到ValueError: attempted relative import beyond top-level package并拔掉头发时,解决方案如下:

您需要在文件层次结构中再高一级

#dir/package/module1/foo.py
#dir/package/module2/bar.pyfrom ..module1 import foo

dir/package/中启动解释器时导入bar.py将导致错误,尽管导入过程永远不会超出当前目录。

dir/中启动解释器时导入bar.py将成功。

对于单元测试:python3 -m unittest discover --start-directory=.成功地从dir/开始工作,但不是从dir/package/开始。

根据Lars的建议,我将这种方法包装在一个实验性的新导入库中:超进口

它让程序员对导入有更多的控制,它允许基于文件系统的导入。因此,你可以从脚本中进行相对导入。父包不是必需的。无论你如何运行你的代码,或者你当前的工作目录是什么,超导入总是有效的,因为超导入使导入变得明确。你不需要改变sys.path,也不需要try/除了块来进行相对导入,有时是绝对导入。

然后你会在somefile.py这样写:

import ultraimportfoo = ultraimport('__dir__/foo.py')

__dir__是somefile.py的目录,也就是调用者。foo.py将与somefile.py.位于同一个目录中

导入这样的脚本时需要注意的是,如果它们包含进一步的相对导入。Ultra队有一个内置的预处理器来将后续的相对导入重写为Ultra队,以便它们继续工作。虽然,这目前有点有限,因为原始Python导入是模棱两可的,你只能做这么多。