从父文件夹导入模块

1086

我正在运行Python 2.5。

这是我的文件夹树:

ptdraft/
  nib.py
  simulations/
    life/
      life.py

(每个文件夹中都有__init__.py,为了可读性已省略)

我应该如何从life模块内部导入nib模块?我希望能在不修改sys.path的情况下完成。

注意:正在运行的主模块位于ptdraft文件夹中。


2
罗斯: 我看过那里了。我该怎么办?我已经有__init__.py文件了。S.Lott: 我不知道该如何检查... - Ram Rachum
4
在shell中输入“echo $PYTHONPATH”,在Python中输入“import sys; print sys.path”,这两个命令可以输出当前系统的Python模块搜索路径。相关信息可以在此链接查看:http://docs.python.org/tutorial/modules.html?highlight=pythonpath#modules - S.Lott
20
我强烈推荐跳过所有关于 sys.path 或者 PYTHONPATH 的答案,查看 np8 的优秀回答。是的,它很长。是的,看起来需要很多工作。但这是唯一一个真正正确且干净地解决问题的答案。 - Aran-Fey
9
可执行的伪代码到底发生了什么?为什么在Python中从父文件夹导入模块如此麻烦?这太荒谬了。 - eric
15
为什么这件事这么麻烦?读完所有的讨论和答案后,仍然没有合理简单的解决方案。 - Apollys supports Monica
显示剩余10条评论
32个回答

752

311
数值错误:在非包中尝试相对导入。 - endolith
24
你需要放在一个包里面,也就是说,你必须有一个__init__.py文件。 - kirbyfan64sos
60
尝试相对导入超出顶级包。 - User
25
请参考以下回答,因为添加 __init__.py 不是你必须做的唯一事情:https://dev59.com/HGgu5IYBdhLWcg3wJT9I#27876800 - Ben Farmer
8
导入错误:尝试使用未知的父包进行相对导入。 - Christoph90
显示剩余9条评论

516

我也在关于从兄弟包中导入的问题上发布了类似的答案。您可以在这里看到。

无需sys.path黑客的解决方案

摘要

  • 将代码封装到一个文件夹中(例如packaged_stuff
  • 创建一个setup.py脚本,在其中使用setuptools.setup()
  • 使用pip install -e <myproject_folder>以可编辑状态安装软件包
  • 使用from packaged_stuff.modulename import function_name进行导入

设置

我假设与问题中相同的文件夹结构

.
└── ptdraft
    ├── __init__.py
    ├── nib.py
    └── simulations
        ├── __init__.py
        └── life
            ├── __init__.py
            └── life.py

我把.叫做根目录,在我的情况下它位于C:\tmp\test_imports

步骤

  1. 在根目录下添加一个setup.py文件 -- setup.py的内容可以简单如下:
    from setuptools import setup, find_packages

    setup(name='myproject', version='1.0', packages=find_packages())

基本上,“任何” setup.py 都可以工作。这只是一个最小的工作示例。

  1. 使用虚拟环境

如果您熟悉虚拟环境,请先激活一个并跳过到下一步。 使用虚拟环境不是必须的,但它们将在长期上帮助您(当您有多个进行中的项目时..)。 最基本的步骤是(在根文件夹中运行)

  • 创建虚拟环境
    • python -m venv venv
  • 激活虚拟环境
    • . venv/bin/activate(Linux)或./venv/Scripts/activate(Win)
  • 停用虚拟环境
    • deactivate(Linux)

要了解更多信息,请谷歌搜索“python virtualenv教程”或类似内容。您可能永远不需要其他命令,只需创建,激活和停用即可。

一旦您创建并激活了虚拟环境,您的控制台应显示虚拟环境的名称。

PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>
  1. 将您的项目以可编辑状态安装

使用pip安装您的顶级包myproject。诀窍是在安装时使用-e标志。这样它将以可编辑状态安装,并且对.py文件所做的所有编辑都将自动包含在安装的包中。

在根目录中运行

pip install -e .(注意点号,它代表“当前目录”)

您还可以通过使用pip freeze来查看其是否已安装

(venv) PS C:\tmp\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
  Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\tmp\test_imports> pip freeze
myproject==1.0
  1. 通过在每个导入前添加 mainfolder 来进行导入

在这个例子中,mainfolder 将会是 ptdraft。这样做的好处是你不会遇到其他模块名称(来自 Python 标准库或第三方模块)的命名冲突。

使用示例

nib.py

def function_from_nib():
    print('I am the return value from function_from_nib!')

life.py

from ptdraft.nib import function_from_nib

if __name__ == '__main__':
    function_from_nib()

运行 life.py

(venv) PS C:\tmp\test_imports> python .\ptdraft\simulations\life\life.py
I am the return value from function_from_nib!

15
为什么使用这种方法比使用sys.path方法更好或更优雅?请您说明。 - Homero Esmeraldo
12
@HomeroBarrocasSEsmeraldo 提出了很好的问题。PYTHONPATH 环境变量没有被改动。这个包会安装在 site-packages 中,它已经存在于 sys.path 中,并且最终用户通常会安装代码在这里。这比使用 sys.path 的 hack 更好(你可以把使用 PYTHONPATH 也归类为路径 hack),因为这意味着开发中的代码应该与其他用户表现一致。相比之下,当使用路径修改时,导入语句会以不同的方式解析。 - wim
18
这绝对是我阅读过的最佳解决方案。它会创建一个指向您当前目录的链接,因此代码的所有更新都可以直接反映到您的工作目录中。如果您需要删除此条目,请手动删除蛋文件,并从dist-packages(如果使用pip,则为site-packages)中删除easy-install中的条目。 - Rakshit Kothari
52
为什么要让Python的导入变得更加复杂呢?我们必须编写一个setup文件,虚拟环境,还要进行pip安装?天哪! - Emerson Xu
155
在Python 3.8版中,导入模块仍需要这么多工作吗?我无法相信这是如此复杂的。 - rocksNwaves
显示剩余29条评论

478

相对导入(如from .. import mymodule)只在包中起作用。 要导入位于当前模块父目录中的“mymodule”:

import os
import sys
import inspect

currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0, parentdir) 

import mymodule

注意:并非总是提供__file__属性。建议使用inspect模块检索当前文件的文件名(和路径),而不是使用os.path.abspath(__file__)

193
sys.path.insert(1, os.path.join(sys.path[0], '..')) 的翻译如下: 在系统路径中插入一个新的路径,该路径是当前文件所在目录的上一级目录。 - b0bz
9
避免使用sys.path.append()而不是insert()的原因是什么? - Tyler
3
@Tyler - 如果在parentdir之外的某个路径中,但在已经指定的sys.path路径中有另一个名为“mymodule”的模块,则这很重要。将parentdir插入sys.path列表的第一个元素可以确保从parentdir导入模块。 - Remi
5
如果您不涉及软件包,那么您提供的方案是最好的。 - Jonathon Reinhart
9
@JHolta的想法很好。sys.path.insert(1, os.path.realpath(os.path.pardir)) 也可以起作用。 - SpeedCoder5
3
实际上,如果您的工作目录与sys.path[0]不同,则使用sys.path[0]是必要的。请参见https://dev59.com/QXRB5IYBdhLWcg3wH0SW#GbikEYcBWogLw_1bB6gH。 - Jimmy He

154

看起来问题与模块所在的父目录等没有关系。

您需要将包含ptdraft的目录添加到PYTHONPATH中。

您说import nib可以工作,这可能意味着您将ptdraft本身(而不是其父目录)添加到了PYTHONPATH中。


64
如果你要处理很多永远不会被重复使用的包,那么把所有东西都添加到pythonpath中并不是一个好的解决方案。 - Jason Cheng
7
那么它们为什么被称为“packages”呢? - Davis Herring
18
这个回答对我很有用。不过为了更加明确,步骤是先import sys,然后再sys.path.append("..\<parent_folder>") - BCJuan
2
@JasonCheng,您不必导出修改后的PYTHONPATH或您的shell配置文件,您也可以选择仅为单个对python的调用设置它(因此仅在每次执行时进行修改),或者将其放入某种.env文件中(每个文件夹基础上)。 - niid

139

您可以在“模块搜索路径”中使用依赖于操作系统的路径,该路径列在sys.path中。

因此,您可以轻松地添加父目录,如下所示:

import sys
sys.path.insert(0, '..')

如果您想要添加父级目录,可以使用“../”来访问上一级目录。
sys.path.insert(0, '../..')

这在Python 2Python 3中都有效。


24
这是相对于当前目录的路径,甚至不一定是包含主脚本的目录。 - Davis Herring
1
这个方法在PyCharm中可以工作,但在VS Code中不行。 - eric xu
对我来说非常好用!谢谢!但是我需要兄弟目录,所以我的解决方案是 sys.path.insert(0,'../sibling_dir') - greenhouse
@DavisHerring 要解决这个问题,可以使用绝对路径。 - Jeyekomon
@Jeyekomon:这只是用另一种易错的方式替换了这种方法中的几种易错形式之一。 - Davis Herring
@DavisHerring 是正确的。你可能想要的是 sys.path.append(os.path.dirname(os.path.abspath(__file__))) - Flimm

93

我不太了解Python 2
Python 3中,可以如下添加父文件夹:

import sys
sys.path.append('..')

...然后就可以从中导入模块。


38
只有在您当前的工作目录是这样的,即 '..' 可以导航到包含所需模块的目录时,此方法才能起作用。 - user5359531
@user5359531是正确的。你可能想要使用sys.path.append(os.path.dirname(os.path.abspath(__file__)))代替。这样做可以确保无论当前工作目录在哪里,都能正常工作。 - Flimm
1
这在VS Code上不起作用,@Flimm的建议适用于VS Code。 - Vespa

65

如果将您的模块文件夹添加到PYTHONPATH中没有起作用,您可以在程序中修改sys.path列表,Python解释器会在其中搜索要导入的模块,Python文档中说:

当导入名为spam的模块时,解释器首先搜索具有该名称的内置模块。 如果找不到,则在变量sys.path给出的目录列表中搜索名为spam.py的文件。 sys.path从以下位置初始化:

  • 包含输入脚本的目录(或当前目录)。
  • PYTHONPATH(目录名称列表,与shell变量PATH具有相同的语法)。
  • 安装相关的默认值。
初始化后,Python程序可以修改sys.path。包含正在运行的脚本的目录被放置在搜索路径的开头,位于标准库路径之前。这意味着该目录中的脚本将被加载,而不是库目录中同名的模块。除非有意替换,否则这是一个错误。
了解这一点后,您可以在程序中执行以下操作:
import sys
# Add the ptdraft folder path to the sys.path list
sys.path.append('/path/to/ptdraft/')

# Now you can import your module
from ptdraft import nib
# Or just
import ptdraft

8
你的答案不错,但并非总是有效且不太具有可移植性。如果程序被移动到一个新位置,/path/to/ptdraft就必须进行编辑。还有一些解决方案可以找出文件所在的当前目录,并以此从父文件夹导入它。 - Edward
1
@Edward,那些技术是什么? - Shuklaswag
1
例如,回答 https://dev59.com/QXRB5IYBdhLWcg3wH0SW#33532002 的 @Shuklaswag。 - Edward

39

这里有一个简单的答案,让你可以看到它是如何工作的,小巧且跨平台。它只使用内置模块(ossysinspect),因此应该可以在任何操作系统上运行,因为 Python 就是为此而设计的。

更短的代码 - 更少的行数和变量

from inspect import getsourcefile
import os.path as path, sys
current_dir = path.dirname(path.abspath(getsourcefile(lambda:0)))
sys.path.insert(0, current_dir[:current_dir.rfind(path.sep)])
import my_module  # Replace "my_module" here with the module name.
sys.path.pop(0)

对于行数少于此数量的代码,请将第二行替换为 import os.path as path, sys, inspect。在 getsourcefile(第3行)的开头添加 inspect. 并删除第一行。

  • 然而,这样导入整个模块可能需要更多时间、内存和资源。

我的答案的代码(较长版本)

from inspect import getsourcefile
import os.path
import sys

current_path = os.path.abspath(getsourcefile(lambda:0))
current_dir = os.path.dirname(current_path)
parent_dir = current_dir[:current_dir.rfind(os.path.sep)]

sys.path.insert(0, parent_dir)

import my_module  # Replace "my_module" here with the module name.

它使用了一个来自Stack Overflow答案的例子如何在Python中获取当前执行文件的路径?,使用内置工具查找正在运行的代码的源(文件名)。

from inspect import getsourcefile
from os.path import abspath

Next, wherever you want to find the source file from you just use:

abspath(getsourcefile(lambda:0))

我的代码将文件路径添加到sys.path,即Python路径列表,因为这样可以让Python从该文件夹导入模块。

在代码中导入模块后,当添加的文件夹中有一个与程序后来导入的另一个模块同名的模块时,建议在新行上运行sys.path.pop(0)。您需要删除导入之前添加的列表项,而不是其他路径。

如果您的程序不导入其他模块,则可以安全地不删除文件路径,因为在程序结束(或重新启动Python shell)后,对sys.path所做的任何编辑都会消失。

关于文件名变量的注意事项

我的答案不使用__file__变量来获取正在运行的代码的文件路径/文件名,因为用户经常将其描述为不可靠。您不应在由其他人使用的程序中使用它来从父文件夹导入模块

以下是一些它无法正常工作的示例(引用自this Stack Overflow问题):

• 它在某些平台上无法找到。有时不是完整的文件路径

  • py2exe没有__file__属性,但有一个解决方法
  • 当您使用execute()从IDLE运行时,没有__file__属性
  • 在我使用NameError:全局名称'__file__'未定义的OS X 10.6中

1
我在导入所需模块后立即使用 sys.path.pop(0) 删除了新路径条目。然而,随后的导入仍然会到达该位置。例如,我有 app/configapp/toolsapp/submodule/config。从 submodule 中,我插入 app/ 来导入 tools,然后删除 app/ 并尝试导入 config,但我得到的是 app/config 而不是 app/submodule/config - user5359531
我发现tools的一个导入也在从父目录导入config。所以,当我稍后尝试在submodule中执行sys.path.pop(0); import config时,期望得到app/submodule/config,实际上却得到了app/config。显然,Python返回了同名模块的缓存版本,而不是实际检查sys.path以匹配该名称的模块。在这种情况下,sys.path被正确地更改,但由于已经加载了同名模块,Python没有进行检查。 - user5359531
我认为这是我遇到的问题的确切参考:https://docs.python.org/3/reference/import.html#the-module-cache - user5359531
以下是来自文档页面5.3.1.模块缓存的一些有用信息: 在导入时,会在sys.modules中查找模块名称*...* sys.modules是可写的。删除一个键将使命名模块的缓存条目无效,导致Python在下次导入时重新搜索*...* 但要注意,如果您保留对模块对象的引用,使其缓存失效,然后重新导入,这两个对象将不同。相比之下,importlib.reload()将重用和重新初始化模块内容*...* - Edward
更简短的代码:os.path.realpath(getsourcefile(lambda:0) + "/../..")。将获取当前源文件并附加两个父目录符号。我没有使用 os.path.sep,因为 getsourcefile 在 Windows 上返回一个使用 / 的字符串。realpath 将从完整路径中弹出两个目录条目(在这种情况下是文件名和当前目录),从而得到父目录。 - Cobertos

38

pathlib库(包含在>= Python 3.4中)使将父目录的路径附加到PYTHONPATH非常简洁和直观:

import sys
from pathlib import Path
sys.path.append(str(Path('.').absolute().parent))

1
能否使用 init.py 来规避这个问题? - ArtificiallyIntelligence
1
谢谢Eric,我刚刚删除了那个令人困惑的部分。 - itmatters
import sys from pathlib import Path sys.path.append(str(Path('.').absolute().parent)) - Feng
FYI,在 Visual Studio Code 调试模式下运行时,这不起作用。在 Python4 中,我希望能够实现全局导入/导出的功能。 - Manuel Hernandez
它在 VS Code 调试模式下对我有效 + 在 Jupyter Notebook 上有效,而不是使用 Path(file)。 - Daniel Malachov
在 Visual Studio Code 中无法正常工作 :( 经常收到错误信息“未找到 sharedFunctions 模块”。 - john k

34

这里有一个更通用的解决方案,它将父目录包含在sys.path中(对我有效):

import os.path, sys
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))

import os, sys\nsys.path.insert(0,os.path.pardir)同样的事情,但更简短 :)(注释中没有换行符) - Anti Veeranna
@antiveeranna,如果您使用os.path.pardir,则无法获得父级的真实路径,只能获取从调用sys.path.insert()的位置开始的相对路径。 - alvas
2
该答案使用了__file__变量,但这个变量可能存在不可靠性问题(并不总是完整的文件路径,在某些操作系统上无法工作等)。正如StackOverflow用户经常提到的那样,将其包含在内的答案会导致更多的问题,并且缺乏跨平台兼容性。想要了解更多信息,请参考https://dev59.com/QXRB5IYBdhLWcg3wH0SW#33532002. - Edward

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接