相对进口数十亿次

564 python import relative-path python-2.7

我来过这里:

还有很多我没有复制的网址,有些在SO上,有些在其他网站上,当我认为我有快速解决方案的时候.

永远反复出现的问题是:使用Windows 7,32位Python 2.7.3,如何解决这个"非包装中尝试相对导入"的消息?我在pep-0328上构建了一个包的精确副本:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py
Run Code Online (Sandbox Code Playgroud)

我确实在适当的模块中创建了名为spam和eggs的函数.当然,它没有用.答案显然在我列出的第4个网址中,但这对我来说都是校友.我访问过的其中一个网址上有此回复:

相对导入使用模块的name属性来确定模块在包层次结构中的位置.如果模块的名称不包含任何包信息(例如,它设置为'main'),则解析相对导入,就像模块是顶级模块一样,无论模块实际位于文件系统的哪个位置.

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

有人可以告诉我为什么Python会给出错误信息,非包装意味着什么!,为什么以及如何定义"包裹",并且准确的答案足以让幼儿园儿童理解.

编辑:导入是从控制台完成的.

Bre*_*arn 848

脚本与模块

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

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

命名

加载文件时,会为其指定一个名称(存储在其__name__属性中).如果它作为顶级脚本加载,则其名称为__main__.如果它作为模块加载,则其名称是文件名,前面是作为其一部分的任何包/子包的名称,以点分隔.

例如,在您的示例中:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

相对导入使用模块的name属性来确定模块在包层次结构中的位置.如果模块的名称不包含任何包信息(例如,它设置为'main'),则解析相对导入,就像模块是顶级模块一样,无论模块实际位于文件系统的哪个位置.

相对进口......

相对导入使用模块的名称来确定它在包中的位置.当您使用相对导入时from .. import foo,这些点表示在包层次结构中增加一些级别.例如,如果您当前模块的名称是package.subpackage1.moduleX,则..moduleA意味着package.moduleA.要from .. 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 其他地方 - 没有内部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).

  • 这应该是所有Python相关导入问题的答案.这应该在文档中,甚至. (88认同)
  • `它的名字没有点,因此你不能在里面使用.. import语句.如果你试图这样做,你将得到"非包装中的相对导入"错误.这从根本上是令人不安的.查看当前目录有什么困难?Python应该能够做到这一点.这是在3x版本中修复的吗? (41认同)
  • 如果你必须花一个小时阅读这篇文章,并且你必须一次又一次地用谷歌搜索它,但仍然只有专家才能明白,那么一个功能就被破坏了。Python 设计者在这个问题上失败了,它应该第一次就能工作,就像任何其他编程语言一样。 (27认同)
  • 请参阅https://www.python.org/dev/peps/pep-0366/ - "请注意,只有顶层包可以通过sys.path访问时,此样板才足够.操作sys.path的其他代码将需要直接执行才能使顶层包装无法导入." - 这对我来说是最令人不安的一点,因为这个"附加代码"实际上很长,并且无法存储在包中的其他位置以便轻松运行. (8认同)
  • 目前,此答案是关于“ __name__”和“ sys.path”的一些重要细节的。具体来说,使用python -m pkg.mod时,将__name__设置为__main__而不是pkg.mod。在这种情况下,使用[`__package__`](https://www.python.org/dev/peps/pep-0366/#proposed-change)解决相对进口问题。同样,当运行`python path / to / script.py`时,Python将脚本的目录而不是当前目录添加到sys.path中。在运行其他大多数方式(包括python -m pkg.mod)时,它将当前目录添加到sys.path中。 (8认同)
  • @Stopforgettingmyaccounts ...:PEP 366显示了它是如何工作的.在文件内部,您可以执行`__package__ ='package.subpackage1'等.然后*即使直接运行,*该文件*将*始终*被视为该包的一部分.如果你对"__package__"有其他疑问,你可能想问一个单独的问题,因为我们在这里解决了你原来的问题. (7认同)
  • 尽管是Python的老手,我仍然会回到这篇文章.对我来说主要的信息是:要么摆弄`sys.path`和`__package__`(这是相当丑陋,请参阅其他答案)或者只是在你的根目录中创建一个"主脚本"`main.py`项目并将所有模块放入子目录中.然后`main.py`可以通过它们的包名直接访问所有模块(=它们所在的相应文件夹的名称). (6认同)
  • 我把这个答案翻译成中文[这里](http://www.laike9m.com/blog/pythonxiang-dui-dao-ru-ji-zhi-xiang-jie,60/). (4认同)
  • 为什么Python文档不能这样读?或者至少在接近可接受性,解决常见误解等方面接近它.想象一下,采用的范围会更广泛! (4认同)
  • 经过数小时的阅读,终于明白了。值得一提的是,如果使用-m,`if __name__ =='__main __'`下的代码仍将运行。查看来自@ user2357112的评论 (4认同)
  • 多谢。Python 文档应该提到这一点 - “相对导入不适用于顶级脚本”。这足以让初学者询问并理解为什么会这样,或者完全忽略该功能。 (4认同)
  • @Stopforgettingmyaccounts ...:我同意这种行为有点令人困惑.它在Python 3中没有改变.但是,它与Python的整体模块/包的原理是一致的,即实际的目录结构不如Python通过`sys.path`等对它的看法那么重要.我自己经常希望Python模块系统是jst目录树的透明反映,但事实并非如此. (3认同)
  • 我的理解是,该段是不完整的:“在加载文件时,会为其指定一个名称(存储在其__name__属性中)。如果以顶级脚本的形式加载,则其名称为__main__。如果是作为模块加载的,则“通过导入模块”添加“,其名称为文件名”,添加“减.py / .pyc”,然后在其之前的所有软件包/子软件包的名称之前添加一部分,用点分隔。”添加“如果通过-m package.module语法作为模块加载,则其名称也为__main__。 (3认同)
  • “Python 将当前目录添加到其搜索路径”让我很困惑,因为我认为这意味着调用目录“python”。确切地说,Python 将*包含顶级脚本的目录*添加到其搜索路径中。此外,如果顶级脚本是符号链接,则*符号链接目标*的当前目录将添加到其搜索路径中。 (2认同)
  • 作为一个使用 pycharm 和 jupyter 开发代码,然后有时将其与 python rq 一起使用作为“任务”的人,我发现不断收到 ImportErrors 非常令人沮丧,特别是当没有充分的理由或解释为什么不允许相对导入时- 只是模糊地提到 __name__ 是 __main__ ,好像这可以解释一切。如果我说 from .mylib import Foo,我并不真正关心我是否在 __main__ (脚本)上下文中——我只想导入我的类。我认为 PEP 警察应该做点什么:-)(好的火焰服,检查一下。) (2认同)
  • 最重要的部分:“脚本不能导入相对” (2认同)

Smi*_*nth 52

外语的答案太多了。所以,我会尽量长话短说。

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

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

  • 这个答案的一部分是错误的。`从. import module` 不是从“包的顶层”导入,而是从与 import 语句所写入的模块相同的包导入,这可能是复杂包层次结构中的某个深层子包。 (3认同)
  • 这是 @BrenBarn 的答案,但这是它的 TL;DR。OP 和其他正在寻找答案的人,就是这样。我花了很长时间才在其他地方找到这个。 (2认同)
  • 更令人困惑的是,当您安装软件包时,绝对导入对我不起作用。我需要使用“.submodule import module”。当我使用“import submodule.module”或“from submodule import module”时,即使该文件夹位于包文件夹中,也找不到它。 (2认同)

小智 29

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

例如,当你在faa.py写道时:

from .. import foo
Run Code Online (Sandbox Code Playgroud)

这具有只有一个意思faa.py识别并加载由蟒,在执行期间,作为一个包的一部分.在这种情况下,该模块的名称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
Run Code Online (Sandbox Code Playgroud)

  • 正确的解决方案是from from __future__ import absolute_import,并强制用户正确使用您的代码...,以便您始终可以执行from。导入foo` (2认同)
  • @Joooeey 不,它是“IDE”。我所知道的唯一没有引号的 IDE 是 Pycharm。 (2认同)

Fed*_*ico 16

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

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()
Run Code Online (Sandbox Code Playgroud)


Ste*_*e L 8

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

if __name__ == '__main__':
   # run test code here...
Run Code Online (Sandbox Code Playgroud)

但是如果我想在同一文件夹中导入其他类或模块,那么我必须将所有导入语句从相对符号更改为本地引用(即删除点 (.))但是在阅读了 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
Run Code Online (Sandbox Code Playgroud)

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

  • 这实际上解决了它。但这确实很恶心。为什么这不是默认行为?! (7认同)
  • “也许这是显而易见的”.. 嗯*那个*代码是显而易见的?无论如何,我都会把它藏在某个地方 - 考虑到我的生死都是由 JetBrains 工具决定的。 (2认同)

tor*_*rek 7

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

我打了几个print电话,以帮助说明这是如何工作的。实际上,您可能希望将其删除(也许还删除该from __future__ import print_function行)。

这个特定的例子太简单了,无法显示何时确实需要在中插入条目sys.path。(见拉尔斯的回答为我们的情况下,需要它,当我们有包目录中的两个或两个以上的水平,然后我们使用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, 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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

    (由于. sys.path,我们并不真正需要添加此路径的绝对的版本。这是一个更深层次的包嵌套结构,我们想要做的from ..otherlib.fileC import ...,有差别。如果你不这样做,就可以在sys.path完全省略所有操作。)

笔记

仍然有一个怪癖。如果从外部运行整个过程:

$ python2 lib.foo
Run Code Online (Sandbox Code Playgroud)

要么:

$ python3 lib.foo
Run Code Online (Sandbox Code Playgroud)

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

Package named 'lib'; __name__ is '__main__'
Run Code Online (Sandbox Code Playgroud)

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

$ python2 lib.foo
Package named 'lib'; __name__ is 'lib.foo'
Package named 'lib'; __name__ is '__main__'
Run Code Online (Sandbox Code Playgroud)

也就是说,该模块被导入两次,一次是通过包导入的,然后是再次导入的,__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__'
Run Code Online (Sandbox Code Playgroud)

警告是新的,但警告说,有关的行为是不能。这就是所谓的双重导入陷阱的一部分。(有关其他详细信息,请参见问题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
Run Code Online (Sandbox Code Playgroud)

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


Bra*_*Dre 6

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

案子

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py
Run Code Online (Sandbox Code Playgroud)

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

解决方案

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

解释

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

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


小智 5

我遇到了类似的问题,我不想更改 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
Run Code Online (Sandbox Code Playgroud)

一种更干净的方法似乎是修改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 *
Run Code Online (Sandbox Code Playgroud)