ChatGPT解决这个技术问题 Extra ChatGPT

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

想象一下这个目录结构:

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)

我建议更新您的问题,以更清楚地说明您正在描述 PEP 366 中解决的问题。
这是一个冗长的解释,但请在此处查看:stackoverflow.com/a/10713254/1267156 我回答了一个非常相似的问题。直到昨晚我都遇到了同样的问题。
对于希望加载位于任意路径的模块的用户,请参阅:stackoverflow.com/questions/67631/…
在相关的说明中,Python 3 将默认情况下将导入的默认处理更改为绝对;必须明确指定相对导入。

J
John B

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

问题是您通过将 mod1.py 作为参数传递给解释器来将模块作为 '__main__' 运行。

PEP 328

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

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

更新:根据 Nick Coghlan 的说法,推荐的替代方法是使用 -m 开关在包内运行模块。


这里的答案涉及在程序的每个入口点处弄乱 sys.path 。我想这是唯一的方法。
推荐的替代方法是使用 -m 开关在包内运行模块,而不是直接指定它们的文件名。
我不明白:答案在哪里?如何在这样的目录结构中导入模块?
@Tom:在这种情况下, mod1 会from sub2 import mod2。然后,要从应用程序内运行 mod1,请执行 python -m sub1.mod1
@XiongChiamiov:这是否意味着如果您的 python 嵌入在应用程序中,您就不能这样做,因此您无法访问 python 命令行开关?
P
Pankaj

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

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

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

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

我在做相对导入时多次遇到这个问题。而且,在阅读了所有先前的答案之后,我仍然无法弄清楚如何以一种干净的方式解决它,而无需将样板代码放入所有文件中。 (虽然有些评论真的很有帮助,但感谢@ncoghlan 和@XiongChiamiov)

希望这对正在与相关进口问题作斗争的人有所帮助,因为通过 PEP 真的不好玩。


最佳答案恕我直言:不仅解释了 OP 出现问题的原因,而且还找到了解决它的方法而不改变他的模块导入的方式。毕竟,OP的相对进口很好。罪魁祸首是直接作为脚本运行时无法访问外部包,-m 旨在解决这一问题。
另请注意:这个答案是问题后 5 年。这些功能当时不可用。
如果您想从同一目录导入模块,您可以执行 from . import some_module
这是帮助我的答案,它也帮助我将我的想法浓缩为:为了运行包含相对导入的 Python 脚本,我必须将脚本作为模块运行,而 $ PWD 是它的父目录比如$ python -m app.main。为清楚起见,$ python -m <main_directory>.<script_with_relative_imports>
n
nosklo
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py

你运行 python main.py。 main.py 确实: import app.package_a.module_a module_a.py 确实 import app.package_b.module_b

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

只要您的 PYTHONPATH 中有 app,它就会起作用。 main.py 可以在任何地方。

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


优秀的答案。有没有办法在不安装 PYTHONPATH 中的包的情况下导入这种方式?
然后,有一天,需要将应用程序的名称更改为 test_app。会发生什么?您将需要更改所有源代码,导入 app.package_b.module_b --> test_app.package_b.module_b。这绝对是不好的做法......我们应该尝试在包中使用相对导入。
l
lesnik

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

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


注意:已经提到的 pep-366(与 pep-3122 几乎同时创建)提供了相同的功能,但使用了不同的向后兼容实现,即,如果您想将包内的模块作为脚本运行 and< /i> 在其中使用显式相对导入,然后您可以使用 -m 开关运行它:python -m app.sub1.mod1 或从顶级脚本调用 app.sub1.mod1.main()(例如,从 setup.py 中定义的 setuptools 的 entry_points 生成)。
+1 使用 setuptools 和入口点 - 这是设置将从外部运行的脚本的正确方法,在明确定义的位置,而不是无休止地破解 PYTHONPATH
没有在 peps 上找到“运行”的定义。对我来说,看起来“运行”并不是最好的定义(对于蚂蚁模式),因为最后“解释”将链接依赖关系,而不是在立即执行的意义上真正“运行”它。 Reference 1reference 2
V
Vit Bernatik

这是100%解决的:

应用程序/ main.py

主文件

设置/local_settings.py

local_settings.py

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

主要.py:

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


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

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

nosklo's答案的解释与示例

注意:所有 __init__.py 个文件都是空的。

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

应用程序/package_a/fun_a.py

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

应用程序/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()

主文件

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 确实:从 app.package_b 导入 fun_b

fun_b.py 从 app.package_a.fun_a 导入 print_a

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


n
new123456
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

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


我正在使用这个片段,结合 imp 模块(如这里解释的 [1])效果很好。 [1]:stackoverflow.com/questions/1096216/…
可能,sys.path.append(path) 应该替换为 sys.path.insert(0, path),sys.path[-1] 应该替换为 sys.path[0]。否则,如果搜索路径中已经存在同名模块,该函数将导入错误的模块。例如,如果当前目录中有“some.py”,import_path("/imports/some.py") 将导入错误的文件。
我同意!有时其他相对进口会优先。使用 sys.path.insert
您将如何复制 from x import y(或 *)的行为?
不清楚,请指定完整使用此脚本来解决OP问题。
G
Garrett Berg

不幸的是,这是一个 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 使用“附加”功能,但是如果您已经定义了一个模块,那将不起作用(我觉得这是非常奇怪的行为)


sys.path.append('../') 适合我(Python 3.5.2)
我认为这很好,因为它将黑客本地化到可执行文件并且不会影响可能依赖于您的包的其他模块。
m
milkypostman

让我把它放在这里供我自己参考。我知道这不是好的 Python 代码,但我需要一个用于我正在处理的项目的脚本,并且我想将该脚本放在 scripts 目录中。

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

C
Community

正如@EvgeniSergeev 在对 OP 的评论中所说,您可以从任意位置的 .py 文件导入代码:

import imp

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

这取自 this SO answer


Y
Yi Jiang

看看http://docs.python.org/whatsnew/2.5.html#pep-328-absolute-and-relative-imports。你可以做

from .mod1 import stuff

除了不能从“主”模块进行相对导入,正如 John B. 的回答所说
j
jung rhew

Python doc

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


A
Andrew_1510

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

bash$ export PYTHONPATH=/PATH/TO/APP

然后:

import sub1.func1
#...more import

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


这实质上就是 virtualenv 让您管理导入语句的方式。
t
the Tin Man

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

我有同样的问题,PEP 328 或 366 都没有完全解决问题,因为据我所知,到一天结束时,都需要将包的头部包含在 sys.path 中。

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


G
Giorgos Myrianthous

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

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

这与操作 sys.path 大致相同,因为 sys.path 是从 PYTHONPATH 初始化的
@Joril 没错,但 sys.path 需要在源代码中硬编码,而 PYTHONPATH 是一个环境变量,可以导出。
R
Rohit Kumar J

此方法查询并自动填充路径:

import os
import inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
os.sys.path.insert(1, parentdir)
# print("currentdir = ", currentdir)
# print("parentdir=", parentdir)

F
F.M.F.

一个 hacky 方法是在运行时将当前目录附加到 PATH 中,如下所示:

import pathlib   
import sys
sys.path.append(pathlib.Path(__file__).parent.resolve())
import file_to_import  # the actual intended import

与此问题的另一个解决方案相比,它使用 pathlib 而不是 os.path