我来过这里:

http://www.python.org/dev/peps/pep-0328/http://docs.python.org/2/tutorial/modules.html#packagesPython包:相对导入python相对导入示例代码不起作用python 2.5中的相对导入Python中的相对导入Python:禁用相对导入

还有很多我没有复制的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

导入是从控制台完成的。

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

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

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

有人能告诉我为什么Python会给出这个错误消息,“非包”是什么意思,为什么以及如何定义“包”,以及准确的答案,让幼儿园的学生很容易理解。


__name__根据所讨论的代码是在全局命名空间中运行还是作为导入模块的一部分运行而改变。

如果代码未在全局空间中运行,__name__将是模块的名称。如果它在全局命名空间中运行——例如,如果您将其键入控制台,或使用python.exe yourscriptnamehere.py将模块作为脚本运行,那么__name__将变为“__main__”。

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

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


脚本与模块

这里有一个解释。简短的版本是,直接运行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__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

如果您导入了moduleX(注意:已导入,未直接执行),其名称将为package.subpackage1.moduleX。如果您导入moduleA,其名称为package.moduleA。但是,如果您直接从命令行运行moduleX,其名称则为__main__,如果您从命令行直接运行moduleA,则其名称为__main__。当模块作为顶级脚本运行时,它将失去其正常名称,其名称改为__main__。

不通过包含的软件包访问模块

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

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

这里是错误消息的关键所在:如果模块的名称没有点,则它不被视为包的一部分。文件实际在磁盘上的位置无关紧要。重要的是它的名称,它的名称取决于您如何加载它。

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

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

相对导入。。。

相对导入使用模块的名称来确定它在包中的位置。使用相对导入时,如从。。导入foo,点表示在包层次结构中增加了一些级别。例如,如果当前模块的名称为package.subpackage1.moduleX,则。。moduleA表示package.moduleA。对于来自..的。。import要工作,模块的名称必须至少有import语句中的点。

…在一个包中只是相对的

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

脚本无法导入相对

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

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

两种解决方案:

如果您确实想直接运行moduleX,但仍然希望它被视为包的一部分,那么可以执行python-m package.subpackage1.moduleX。-m告诉python将其作为模块加载,而不是作为顶级脚本加载。或者您实际上不想运行moduleX,只想运行其他脚本,比如myfile.py,它使用moduleX内部的函数。如果是这种情况,请将myfile.py放在其他地方,而不是放在包目录中,然后运行它。如果在myfile.py中,您可以执行类似于从package.moduleA导入垃圾邮件的操作,那么它会正常工作。

笔记

对于这两种解决方案,包目录(示例中的包)必须可以从Python模块搜索路径(sys.path)访问。否则,您将无法可靠地使用包中的任何内容。从Python 2.6开始,模块用于包解析的“名称”不仅由__name__属性决定,还由__package__属性决定。这就是为什么我避免使用显式符号__name__来引用模块的“名称”。由于Python 2.6,模块的“名称”实际上是__package__+'.'+__name__,如果__package__为None,则仅为__name__。)


以下是一个我不建议使用的解决方案,但在某些情况下可能很有用,因为模块根本没有生成:

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()

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

所以我使用了以下黑客。不幸的是,它依赖于imp模块自从3.4版本被弃用,转而使用importlib。(importlib也有可能吗?我不知道。)不过,目前黑客还是可以使用的。

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

#!/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 *

这里有一个通用的方法,经过修改以适合作为示例,我现在正在使用它来处理作为包编写的Python库,这些库包含相互依赖的文件,我希望能够在其中逐个测试其中的部分。让我们调用这个lib.foo,假设它需要访问函数f1和f2的lib.fileA,以及Class3类的lib.file B。

我加入了一些打印调用,以帮助说明这是如何工作的。实际上,您可能希望删除它们(也可能删除from __future_import print_function行)。

这个特定的例子太简单了,无法显示何时我们真的需要在sys.path中插入一个条目。(当我们有两个或更多级别的包目录,然后使用os.path.dirname(os.path.deirname(__file__))时,请参阅Lars的回答,了解我们确实需要它的情况,但在这里也不会有什么问题。)在sys.path测试中不使用if_i也足够安全。但是,如果每个导入的文件都插入了相同的路径,例如,如果fileA和fileB都想从包中导入实用程序,这会多次将sys.path与相同的路径混淆,因此最好在样板中的sys.path中不包含if_i。

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和python3.x中,这些功能都是相同的):

如果作为import lib或from lib运行import foo作为从普通代码导入的常规包,__package是lib,__name__是lib.foo。我们采用第一个代码路径,从.fileA导入,等等。如果以python-lib/foo.py运行,__package__将为None,__name__将为__main__。我们采用第二个代码路径。lib目录已经在sys.path中,因此不需要添加它。我们从fileA等导入。如果在lib目录中作为python-foo.py运行,则行为与情况2相同。如果在lib目录中以python-mfoo的形式运行,则行为类似于情况2和3。但是,指向lib目录的路径不在sys.path中,因此我们在导入之前添加它。如果我们运行Python,然后导入foo,这同样适用。(由于.在sys.path中,所以我们实际上不需要在这里添加路径的绝对版本。这就是我们希望从.otherlib.fileC import…中执行的更深层次的包嵌套结构的地方。如果不这样做,可以完全省略所有sys.path操作。)

笔记

还有一个怪癖。如果你在外面做这件事:

$ python2 lib.foo

or:

$ python3 lib.foo

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

Package named 'lib'; __name__ is '__main__'

但如果lib/__init__.py本身导入例程,以便它可以将routine.name直接导出为lib.name,则会得到:

$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__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__'

警告是新的,但关于行为的警告不是。这是一些人所说的双重进口陷阱的一部分。(更多详情请参见第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副本时,删除一个_ii副本)。


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

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


这确实是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

因此,在与许多其他人一起对此吹毛求疵之后,我在本文中看到了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测试的模块中使用。也许这是显而易见的,但我在这里提供了这条注释,以防其他人对上述相关进口问题感到沮丧,可以利用它。


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

案例

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

使用我们熟悉的示例,并在其中添加moduleX.py对..的相对导入。。模块A。假设我尝试在导入moduleX的subpackage1目录中编写测试脚本,但随后出现了OP描述的可怕错误。

解决方案

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

解释

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

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


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

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

如果从上面的包目录中像这样启动py-m package.module,那么python知道顶层在哪里


在大多数情况下,当我看到ValueError:试图在顶级包之外进行相对导入并拔出头发时,解决方案如下:

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

#dir/package/module1/foo.py

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

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

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

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


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

它使程序员能够更好地控制导入,并允许基于文件系统的导入。因此,您可以从脚本进行相对导入。父包不是必需的。无论您如何运行代码或当前的工作目录是什么,ultraimport都将始终有效,因为ultraimport使导入变得明确。您不需要更改sys.path,也不需要try/except块来有时执行相对导入,有时执行绝对导入。

然后你可以在somefile.py中写下类似的内容:

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

__dir__是superimport()的调用程序somefile.py的目录。foo.py将与somefile.py位于同一目录中。

像这样导入脚本时需要注意的一点是,如果它们包含进一步的相对导入。ultraimport有一个内置的预处理器,用于将后续的相对导入重写为ultraimports,以便它们继续工作。尽管如此,由于最初的Python导入是不明确的,所以目前这有点有限,您只能做这么多。