如何在Python中进行相对导入?


527

想象一下这个目录结构:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

我正在编码mod1,我需要从中导入一些东西mod2。我该怎么办?

我尝试过,from ..sub2 import mod2但是得到了“未打包的相对导入尝试”。

我四处搜寻,但只发现“ sys.path操纵”骇客。有没有一种干净的方法?


编辑:我所有__init__.py的当前为空

EDIT2:我想这样做,因为SUB2包含了为子包(共享类sub1subX等等)。

Edit3:我要寻找的行为与PEP 366中描述的相同(感谢John B)


8
我建议您更新的问题,使之更加清楚,你所描述的问题在PEP 366解决
约翰·B

2
这是一个冗长的解释,但请在此处查看:stackoverflow.com/a/10713254/1267156我回答了一个非常类似的问题。直到昨晚我也遇到了同样的问题。
Sevvy325

3
对于那些谁愿意来加载位于任意路径的模块,看到这一点:stackoverflow.com/questions/67631/...
叶夫根谢尔盖耶夫

2
值得一提的是,Python 3会将默认的默认导入处理更改为绝对处理;相对进口必须明确规定。
罗斯

Answers:


337

每个人似乎都想告诉您应该做什么,而不仅仅是回答问题。

问题是您通过将mod1.py作为参数传递给解释器,从而以“ __main__”的身份运行模块。

PEP 328

相对导入使用模块的__name__属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,将其设置为“ __main__”),则相对导入将被视为模块是顶级模块,而不论该模块实际位于文件系统上的哪个位置。

在Python 2.6中,他们添加了相对于主模块引用模块的功能。 PEP 366说明了更改。

更新:根据Nick Coghlan的建议,推荐的替代方法是使用-m开关运行软件包中的模块。


2
答案是在程序的每个入口点都弄乱sys.path。我想那是唯一的方法。
尼克·雷塔拉克

76
推荐的替代方法是使用-m开关运行包内的模块,而不是直接指定其文件名。
ncoghlan 2011年

127
我不明白:答案在哪里?如何在这样的目录结构中导入模块?
汤姆(Tom)

27
@Tom:在这种情况下,mod1将from sub2 import mod2。然后,要在app中运行mod1,请执行do python -m sub1.mod1
熊加米奥夫

11
@XiongChiamiov:这是否意味着如果您的python嵌入在应用程序中,您将无法执行此操作,因此您无权访问python命令行开关?
LarsH 2013年

130

这是对我有效的解决方案:

我执行as的相对导入 from ..sub2 import mod2 ,然后,如果要运行,mod1.py则转到的父目录,app并使用python -m开关as运行模块 python -m app.sub1.mod1

相对导入发生此问题的真正原因是,相对导入通过获取__name__模块的属性起作用。如果模块直接运行,则__name__设置为__main__,并且不包含有关包结构的任何信息。并且,这就是为什么python抱怨该relative import in non-package错误的原因。

因此,通过使用-m开关,您可以将包结构信息提供给python,通过它可以成功解析相对导入。

在进行相对导入时,我多次遇到此问题。而且,在阅读了所有先前的答案之后,我仍然无法弄清楚如何解决此问题,而无需在所有文件中添加样板代码。(尽管有些评论确实很有帮助,这要感谢@ncoghlan和@XiongChiamiov)

希望这对正在解决相对进口问题的人有所帮助,因为通过PEP确实很有趣。


9
恕我直言的最佳答案:不仅解释了OP为何会出现此问题,而且找到了解决该问题的方法,而无需更改其模块导入的方式。毕竟,OP的相对进口还不错。罪魁祸首是直接作为脚本运行时无法访问外部软件包,这-m是设计来解决的。
MestreLion

26
还要注意:这个答案距问题5年。这些功能当时不可用。
JeremyKun 2014年

1
如果要从同一目录导入模块,则可以执行from . import some_module
Rotareti

124
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. 你跑python main.py
  2. main.py 确实: import app.package_a.module_a
  3. module_a.py 确实 import app.package_b.module_b

另外2或3可以使用: from app.package_a import module_a

只要您app在PYTHONPATH中具有此功能,便可以使用。main.py可能在那时的任何地方。

因此,您编写了一个setup.py将整个应用程序包和子包复制(安装)到目标系统的python文件夹以及main.py目标系统的脚本文件夹的代码。


3
极好的答案。有没有可以在PYTHONPATH中不安装软件包的方式导入的方法?
auraham 2012年


6
然后,有一天需要将应用名称更改为test_app。会发生什么?您将需要更改所有源代码,导入app.package_b.module_b-> test_app.package_b.module_b。这绝对是错误的做法...而且我们应该尝试在包中使用相对导入。
Spybdai '16

49

“ Guido将程序包中正在运行的脚本视为反模式”(被拒绝的 PEP-3122

我花了很多时间试图找到解决方案,在Stack Overflow上阅读了相关文章,并对自己说:“一定有更好的方法!”。好像没有。


10
注意:已经提到过pep-366(与pep-3122大约在同一时间创建)提供了相同的功能,但是使用了不同的向后兼容实现,即,如果您想将包中的模块作为脚本运行使用显式相对导入在其中,您可以使用-mswitch:运行它,python -m app.sub1.mod1app.sub1.mod1.main()从顶级脚本调用(例如,从setup.py中定义的setuptools的entry_points生成)。
jfs 2013年

+1用于使用setuptools和入口点-这是在正确定义的位置设置将从外部运行的脚本的正确方法,而不是无休止地攻击PYTHONPATH
RecencyEffect

38

这可以100%解决:

  • 应用/
    • main.py
  • 设置/
    • local_setings.py

在app / main.py中导入settings / local_setting.py:

main.py:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')

2
谢谢!所有的人都强迫我以不同的方式运行脚本,而不是告诉我如何在脚本中解决它。但是我不得不更改要使用的代码sys.path.insert(0, "../settings"),然后from local_settings import *
Vit Bernatik

25
def import_path(fullpath):
    """ 
    Import a file with full path specification. Allows one to
    import from anywhere, something __import__ does not do. 
    """
    path, filename = os.path.split(fullpath)
    filename, ext = os.path.splitext(filename)
    sys.path.append(path)
    module = __import__(filename)
    reload(module) # Might be out of date
    del sys.path[-1]
    return module

我正在使用此代码片段从路径导入模块,希望对您有所帮助


2
我正在使用此代码段,并将其与imp模块结合使用(如此处[1]所述),效果很好。[1]:stackoverflow.com/questions/1096216/...
雄Chiamiov

7
可能是sys.path.append(path)应该替换为sys.path.insert(0,path),而sys.path [-1]应该替换为sys.path [0]。否则,如果搜索路径中已经存在相同名称的模块,则该函数将导入错误的模块。例如,如果当前目录中有“ some.py”,则import_path(“ / imports / some.py”)将导入错误的文件。
Alex Che 2010年

我同意!有时其他相对进口将占优势。使用sys.path.insert
iElectric 2010年

您将如何复制from x import y(或*)的行为?
levesque 2010年

目前尚不清楚,请指定此脚本的完整用法以解决OP问题。
mrgloom 18/09/24

21

nosklo's举例说明答案

注意:所有__init__.py文件均为空。

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

app / package_a / fun_a.py

def print_a():
    print 'This is a function in dir package_a'

app / package_b / fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

main.py

from app.package_b import fun_b
fun_b.print_b()

如果运行,$ python main.py它将返回:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.py可以: from app.package_b import fun_b
  • fun_b.py确实 from app.package_a.fun_a import print_a

所以文件夹中的文件package_b使用了文件夹中的文件package_a,这就是您想要的。对??


12

不幸的是,这是一个sys.path hack,但是效果很好。

我在另一层遇到了这个问题:我已经有一个具有指定名称的模块,但这是错误的模块。

我想要做的是以下操作(我正在使用的模块是module3):

mymodule\
   __init__.py
   mymodule1\
      __init__.py
      mymodule1_1
   mymodule2\
      __init__.py
      mymodule2_1


import mymodule.mymodule1.mymodule1_1  

请注意,我已经安装了mymodule,但是在我的安装中我没有“ mymodule1”

而且我会收到一个ImportError,因为它试图从安装的模块中导入。

我试图做一个sys.path.append,但是没有用。起作用的是sys.path.insert

if __name__ == '__main__':
    sys.path.insert(0, '../..')

有点骇客,但一切正常!因此请记住,如果要决定覆盖其他路径,则需要使用sys.path.insert(0,pathname)使其起作用!这对我来说是一个非常令人沮丧的症结所在,有人说要在sys.path中使用“ append”功能,但是如果您已经定义了一个模块,那是行不通的(我发现这是非常奇怪的行为)


sys.path.append('../')对我来说效果很好(Python 3.5.2)
Nister

我认为这很好,因为它可以将hack本地化为可执行文件,并且不会影响可能取决于您的软件包的其他模块。
汤姆·罗素

10

让我将其放在此处以供我自己参考。我知道这不是很好的Python代码,但是我需要一个脚本来处理我正在处理的项目,所以我想将脚本放在scripts目录中。

import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

9

正如@EvgeniSergeev在对OP的注释中所说,您可以使用以下.py命令从任意位置的文件导入代码:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

这是从此SO答案中获取的



2

Python文档中

在Python 2.5中,您可以使用from __future__ import absolute_import指令将导入的行为切换为绝对导入。在将来的版本(可能是Python 2.7)中,此绝对导入行为将成为默认设置。一旦将绝对导入设置为默认设置,import string就将始终找到标准库的版本。建议用户应尽可能多地开始使用绝对导入,因此最好开始编写from pkg import string代码


1

我发现将“ PYTHONPATH”环境变量设置为顶层文件夹更容易:

bash$ export PYTHONPATH=/PATH/TO/APP

然后:

import sub1.func1
#...more import

当然,PYTHONPATH是“全局”的,但它并没有给我带来麻烦。


从本质上讲virtualenv,这就是使您管理导入语句的方式。
byxor

1

除了John B所说的,似乎设置__package__变量应该有帮助,而不是更改变量__main__可能会搞砸其他事情。但是据我测试,它并不能完全正常工作。

我遇到了同样的问题sys.path,而据我所知,PEP 328或366都无法完全解决问题,因为两者在一天结束时都需要将包装的标头包括在其中。

我还要提及的是,我没有找到如何格式化应该放入这些变量的字符串的格式。是"package_head.subfolder.module_name"还是什么?


0

您必须将模块的路径附加到PYTHONPATH

export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"

1
这与操作大致相同sys.path,因为它是sys.pathPYTHONPATH
Joril

@Joril是正确的,但是sys.pathPYTHONPATH环境变量不同,可以将其导出,而需要在源代码中进行硬编码。
Giorgos Myrianthous
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.