如何在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)

386983 次浏览

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

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

从# EYZ0:

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

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

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

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.pyimport app.package_b.module_b

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

只要PYTHONPATH中有app,这就可以工作。main.py可能在任何地方。

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

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

我使用这个代码片段从路径导入模块,希望有帮助

从# EYZ0,

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

"Guido将包中运行的脚本视为反模式"(拒绝 # EYZ0) < / p >

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

很不幸,这是一个系统。路径hack,但它工作得很好。

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

我想做的是以下(我从模块是模块3):

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




import mymodule.mymodule1.mymodule1_1

注意,我已经安装了我的模块,但在我的安装中,我没有“mymodule1”

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

我尝试了一个sys。path。追加,这行不通。sys.path.insert是什么

if __name__ == '__main__':
sys.path.insert(0, '../..')
这样的一种hack,但它都工作! 所以请记住,如果你想要你的决定覆盖其他路径,那么你需要使用sys.path。插入(0,pathname)让它工作!这对我来说是一个非常令人沮丧的症结,很多人说要使用“追加”功能到sys。路径,但如果你已经有一个模块定义(我发现这是非常奇怪的行为)

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

bash$ export PYTHONPATH=/PATH/TO/APP

然后:

import sub1.func1
#...more import

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

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

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

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

我把这个写在这里供我自己参考。我知道这不是很好的Python代码,但我需要一个我正在做的项目的脚本,我想把脚本放在scripts目录下。

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

下面是一个适合我的解决方案:

我做的相对导入为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提供包结构信息,通过这些信息,python可以成功地解析相对导入。

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

希望这能帮助那些与相对进口问题作斗争的人,因为通过PEP真的不好玩。

用例子解释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()

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文件夹,这是你想要的。对吧? ?

正如@EvgeniSergeev在OP的注释中所说,你可以从.py文件中任意位置导入代码:

import imp


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

这是来自这个SO答案

这是100%解决的问题:

    <李>应用程序/
    • main.py
    • 李< / ul > < / > <李>设置/
      • local_setings.py
      • 李< / ul > < / >

      在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')
      

你必须添加模块的路径到PYTHONPATH:

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

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

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)

一种简单的方法是在运行时将当前目录追加到PATH,如下所示:

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

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