从父文件夹导入模块


624

我正在运行Python 2.5。

这是我的文件夹树:

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

(我还在__init__.py每个文件夹中,为便于阅读,在此省略)

如何nib从模块内部导入life模块?我希望无需修补sys.path就可以做到。

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


1
您的PYTHONPATH设置是什么?
S.Lott

2
罗斯:我看着那里。我该怎么办?我已经有一个__init__.py。S.Lott:我不知道该怎么检查...
Ram Rachum

4
从外壳回显$ PYTHONPATH;进口系统; 从Python内部打印sys.path。 docs.python.org/tutorial/...
美国洛特

@FlipMcF Google是冒泡的搜索引擎,因此,此结果对您而言相当高的事实并不重要。更重要的是,非冒泡的搜索引擎DuckDuckGo也对此排名很高。
ArtOfWarfare 2015年

3
我强烈建议跳过所有答案sys.pathPYTHONPATH答案,并查看np8的出色答案。是的,它读得很长。是的,这看起来需要很多工作。但这是正确正确地解决问题的唯一答案。
阿兰·菲

Answers:


118

看来问题与该模块位于父目录或类似目录中无关。

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

您说过import nib与您合作,这可能意味着您将ptdraft自身(而不是其父项)添加到了PYTHONPATH中。


15
如果要使用许多永远不会被重用的软件包,则必须将所有内容添加到pythonpath中并不是一个好的解决方案。
Jason Cheng

@JasonCheng:那为什么要打包
戴维斯·鲱鱼

6
对我来说,这个答案已经解决了。但是,为了使其更加明确,过程是至import sys然后sys.path.append("..\<parent_folder>")
BCJuan

603

您可以使用相对导入(python> = 2.5):

from ... import nib

(Python 2.5的新增功能)PEP 328:绝对导入和相对导入

编辑:添加了另一个点“。” 上两个包


177
ValueError: Attempted relative import in non-package
Endlith 2013年

18
@endolith:您必须位于一个包中,即,您必须具有init .py文件。
kirbyfan64sos

31
尝试了超出顶级软件包的相对导入
用户

291
为了更精确,您需要一个__init__.py文件。
kara deniz 2014年

17
另请参阅下面的答案,因为加入__init__.py是不是你所要做的唯一事情:stackoverflow.com/questions/11536764/...
奔农民

286

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

import os,sys,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__属性并不总是给定的。os.path.abspath(__file__)我现在建议不要使用Inspect模块来检索当前文件的文件名(和路径),而不要使用它


97
简短一些:sys.path.insert(1, os.path.join(sys.path[0], '..'))
JHolta

8
有什么理由要避免sys.path.append()而不是插入?
泰勒(Tyler)

2
@Tyler-在之外的其他地方可能很重要parentdir,但是在中已经指定的路径之一中sys.path,有另一个名为“ mymodule”的模块。插入parentdir作为sys.path列表的第一个元素可确保从中parentdir导入模块。
雷米

5
@JHolta如果您不处理软件包,则最好的解决方案是。
乔纳森·莱因哈特2013年

5
@JHolta好主意。sys.path.insert(1, os.path.realpath(os.path.pardir))也可以。
SpeedCoder5

175

对于同级软件包的导入问题,我也发表了类似的答案。你可以在这里看到它。

没有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)将A添加setup.py到根文件夹

的内容setup.py可以很简单

from setuptools import setup, find_packages

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

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

2)使用虚拟环境

如果您熟悉虚拟环境,请激活一个,然后跳到下一步。虚拟环境的使用不是绝对必需的,但从长远来看(当您正在进行多个项目时),它们确实可以帮助您。最基本的步骤是(在根文件夹中运行)

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

要了解更多有关此的信息,只需在Google上搜索“ 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>

3)pip以可编辑状态安装项目

安装您的顶级包myproject使用pip。诀窍是-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

4)通过mainfolder在每次导入之前进行导入

在此示例中,mainfolder将为ptdraft。这样的好处是您不会与其他模块名称(来自python标准库或3rd party模块)发生名称冲突。


用法示例

笔尖

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!

1
为什么这样做?是否以某种方式改变了幕后的PYTHONPATH?
Homero Esmeraldo

2
另外,为什么这比使用sys.path方法更好或更优雅?
Homero Esmeraldo

5
@HomeroBarrocasSEsmeraldo好的问题。PYTHONPATH env var保持不变。这将安装到site-packages中,该站点包已经sys.path存在,最终用户通常会将代码安装在该站点中。这比sys.path黑客更好(并且您可以在路径黑客的类别中包括使用PYTHONPATH),因为这意味着开发中的代码对您的行为应与对其他用户的行为相同。相反,在使用路径修改时,将以不同的方式解析import语句。
wim

5
到目前为止,这是我读过的最好的解决方案。这将创建一个到当前目录的链接,因此对代码的所有更新都将直接反映到您的工作目录中。如果需要删除该条目,请手动删除egg文件,并从dist-packages(如果使用pip,则为site-packages)中的easy-install中删除该条目。
Rakshit Kothari,

2
为什么使python导入头痛变得更加复杂?我们必须编写一个安装文件,一个虚拟环境,一个pip安装程序?我的天啊!
Emerson Xu

68

您可以在sys.path中列出的“模块搜索路径”中使用取决于OS的路径。因此您可以轻松添加父目录,如下所示

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

如果您要添加父/母目录,

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

这在python 2和3。


6
这是相对于当前目录的,甚至不一定是包含主脚本的目录。
戴维斯鲱鱼

57

如果无法将模块文件夹添加到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

6
您的回答是好的,但可能并不总是有效,而且不太便于携带。如果程序已移至新位置,/path/to/ptdraft则必须进行编辑。有一些解决方案可以计算出文件的当前目录,也可以通过这种方式从父文件夹中将其导入。
爱德华

@Edward那些技巧是什么?
Shuklaswag

1
例如@Shuklaswag,答案stackoverflow.com/questions/714063/…
爱德华

50

对python 2不太了解。
在python 3中,可以按以下方式添加父文件夹:

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

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


13
仅当您当前的工作目录'..'指向包含相关模块的目录时,此方法才有效。
user5359531

31

这是一个简单的答案,因此您可以了解它的工作原理(小型和跨平台)。
它仅使用内置模块(ossysinspect),所以应该工作
在任何操作系统(OS),因为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,在(第3行)的开头
添加inspect.getsourcefile然后删除第一行。
-但是,这会导入所有模块,因此可能需要更多的时间,内存和资源。

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

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  

接下来,无论您想在哪里找到源文件,都只需使用:

abspath(getsourcefile(lambda:0))

我的代码sys.path在的python路径列表中添加了文件路径
因为这允许Python从该文件夹导入模块。

在代码中导入模块之后, 当添加的文件夹中的模块名称与另一个 稍后在程序中导入的模块同名时,最好sys.path.pop(0)换行运行。您需要删除导入之前添加的列表项,而不是其他路径。 如果您的程序未导入其他模块,则不删除文件路径是安全的,因为 在程序结束(或重新启动Python Shell)之后,对



sys.path消失。

有关文件名变量的注释

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

一些不起作用的示例(引用 Stack Overflow问题):

• 在某些平台找不到•有时不是完整的文件路径

  • py2exe没有__file__属性,但是有一种解决方法
  • 当您从IDLE运行时,execute()没有__file__属性
  • 我得到的OS X 10.6 NameError: global name '__file__' is not defined

1
我这样做并sys.path.pop(0)在导入所需模块后立即删除了新路径条目 。但是,随后的进口仍运往该地点。例如,我有app/configapp/toolsapp/submodule/config。从中submodule,我插入app/导入tools,然后删除app/并尝试导入config,但是我得到app/configapp/submodule/config
user5359531

我发现,tools的导入之一也是config从父目录导入的。所以当我以后尝试在sys.path.pop(0); import config室内做submodule,期望得到时 app/submodule/config,我实际上是在得到app/config。显然,Python返回具有相同名称的模块的缓存版本,而不是实际检查sys.path与该名称匹配的模块的。sys.path在这种情况下,正确地进行了更改,由于已经加载了相同名称的模块,Python并未对其进行检查。
user5359531

我认为这是我遇到的问题的确切参考:docs.python.org/3/reference/import.html#the-module-cache
user5359531

来自5.3.1的一些有用信息文档页面上的模块缓存:导入期间,在sys.modules ... sys.modules中可查询模块名称。删除键将使命名模块的缓存条目无效,从而导致Python在其下一次导入时重新搜索。...但是要当心,就像您保留对模块对象的引用一样,使它的缓存无效,然后重新导入,这两个对象将有所不同。相比之下,importlib.reload()将重用并重新初始化模块内容...
爱德华

甚至更短:os.path.realpath(getsourcefile(lambda:0) + "/../..")。将获得当前源文件,并附加两个父目录符号。我没有使用os.path.sepas getsourcefile/即使在Windows上也使用返回字符串。realpath将负责从完整路径(在本例中为文件名,然后是当前目录)中弹出两个目录条目,从而给出父目录。
Coburn

28

这是更通用的解决方案,其中将父目录包含在sys.path中(对我有用):

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

import os, sys\n sys.path.insert(0,os.path.pardir) 同样的事情,但排序:)(注释中没有换行)
Anti Veeranna

@antiveeranna,如果您使用的os.path.pardir话,将不会获得父母的sys.path.insert()
真实

1
正如StackOverflow用户经常提到的,此答案使用的__file__变量可能不可靠(并不总是完整的文件路径,不适用于所有操作系统等)。将答案更改为不包括答案,将减少问题,并且更加兼容。有关更多信息,请参见stackoverflow.com/a/33532002/3787376
爱德华

22

我发现以下方法可用于从脚本的父目录导入包。在示例中,我想env.pyapp.db包中导入函数。

.
└── my_application
    └── alembic
        └── env.py
    └── app
        ├── __init__.py
        └── db
import os
import sys
currentdir = os.path.dirname(os.path.realpath(__file__))
parentdir = os.path.dirname(currentdir)
sys.path.append(parentdir)

因此,一个不错的选择:sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
juzzlin

sys.path通常根本不建议进行修改。你需要某种方式(例如PYTHONPATH)你的库是可访问的其他代码(或者它不是一个真正的图书馆); 一直使用它!
戴维斯·鲱鱼

14

上述解决方案也很好。解决此问题的另一种方法是

如果要从顶层目录导入任何内容。然后,

from ...module_name import *

另外,如果要从父目录导入任何模块。然后,

from ..module_name import *

另外,如果要从父目录导入任何模块。然后,

from ...module_name.another_module import *

这样,您可以根据需要导入任何特定方法。


6
看起来应该是这样,但是由于某种原因,它的工作方式与sys.path.append上面的技巧不同,因此我无法像这样导入我的库。这是我在开始使用Google之前首先尝试做的事情:)好的,就是这个ValueError: attempted relative import beyond top-level package
juzzlin

10

对我来说,访问父目录最短和最喜欢的oneliner是:

sys.path.append(os.path.dirname(os.getcwd()))

要么:

sys.path.insert(1, os.path.dirname(os.getcwd()))

os.getcwd()返回当前工作目录的名称,os.path.dirname(directory_name)返回所传递目录的目录名称。

实际上,在我看来,Python项目体系结构应采用以下方式:子目录中的任何模块都不会使用父目录中的任何模块。如果发生这种情况,则值得重新考虑项目树。

另一种方法是将父目录添加到PYTHONPATH系统环境变量。


2
+1表示必须重组项目(而不是对问题进行
整顿

这没有父母,而是父母
Ricky Levi

9

在Jupyter笔记本中

只要您在Jupyter Notebook中工作,这个简短的解决方案就可能有用:

%cd ..
import nib

即使没有__init__.py文件也可以使用。

我在Linux和Windows 7上使用Anaconda3对其进行了测试。


2
%cd -导入后添加是没有意义的,因此笔记本的当前目录保持不变。
Dror


5

当不在带有__init__.py文件的打包环境中时,pathlib库(包含在> = Python 3.4中)使将父目录的路径附加到PYTHONPATH变得非常简洁直观:

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

是否可以使用init .py来解决此问题?
人工智能

4

与过去的答案相同的风格-但行数较少:P

import os,sys
parentdir = os.path.dirname(__file__)
sys.path.insert(0,parentdir)

文件返回您正在工作的位置


对我来说,文件包含路径的文件名。我使用“ ipy filename.py”运行它。
Curtis Yallop 2014年

嗨,@ CurtisYallop在上面的示例中,我们正在添加包含python文件所在文件的目录(我们当前位于)。带有文件名的os.path.dirname调用应返回文件的路径,我们将THAT添加到路径中,而不是显式添加文件-HTH :-)
YFP 2014年

您假设__file__始终包含路径和文件。有时,它仅包含文件名而不包含路径。
Curtis Yallop 2014年

@CurtisYallop-一点也不,先生,我假设文件是文件名,并且我正在使用os.path.dirname()来获取文件的路径。您有一个不起作用的例子吗?我希望复制的步骤
YFP 2014年

1
@CurtisYallop-不,我不是!我的意思是:print / _ / file / _ /将为您提供示例中“ file.py”上方的文件名-然后,您可以使用os.path.dirname()从中获取完整路径。如果您是从某个怪异的地方打电话,并且希望建立这种关系,则可以通过os模块轻松找到工作目录-Jeez!
YFP 2014年

1

使用库。创建一个名为nib的库,使用setup.py安装它,使其驻留在站点程序包中,您的问题将得到解决。您不必将自己制作的所有东西都塞进一个包装中。分解成碎片。


1
您的想法很好,但是有些人可能希望将其模块与代码捆绑在一起-也许是为了使其具有可移植性,而不是site-packages每次在其他计算机上运行模块时都不需要将其模块放入其中。
爱德华

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.