从父文件夹导入模块

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个回答

6

这是与以往答案相同的风格,但代码行数更少 :P

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

file 返回你当前所在的位置。


对我而言,file 是不包含路径的文件名。我使用“ipy filename.py”来运行它。 - Curtis Yallop
@CurtisYallop - 不是的,先生,我假设__file__是文件名,并使用os.path.dirname()获取文件路径。您有这种情况不起作用的示例吗?我很想知道如何重现。 - YFP
你假设 __file__ == "C:\dir\file.py",但实际上是 "file.py"。对我来说,它是 "file.py"。os.path.dirname(x) 返回 "x" 的路径。它会检查字符串 "x",解析并提取字符串开头的路径。对于 "C:\dir\file.py",它是 "C:\dir"。对于 "file.py",它为空。如果字符串中没有包含路径,则无法找到任何路径。如果你创建了文件 "file.py",并将上面的注释中的代码插入其中,然后像这样执行它 "python file.py",那么 parentdir 就是空的。 - Curtis Yallop
1
@CurtisYallop - 不是的!我是在说:print //file// 将会给你文件名,在你上面的例子中是 "file.py" - 然后我在说你可以使用 os.path.dirname() 从中获取完整路径。如果你是从某个奇怪的位置调用并且想要相对路径,那么你可以通过 os 模块轻松地找到你的工作目录 -- 天哪! - YFP
此答案使用__file__变量,该变量可能不可靠(并非始终是完整的文件路径,在每个操作系统上都无法工作等),正如StackOverflow用户经常提到的那样。更改答案以不包括它将会导致更少的问题,并且更具跨兼容性。有关更多信息,请参见https://dev59.com/QXRB5IYBdhLWcg3wH0SW#33532002。 - Edward
显示剩余3条评论

2

在我看来,你似乎不需要导入父模块。假设在 nib.py 中你有 func1() 和 data1,你需要在 life.py 中使用。

nib.py

import simulations.life.life as life
def func1():
   pass
data1 = {}
life.share(func1, data1)

life.py

func1 = data1 = None

def share(*args):
   global func1, data1
   func1, data1 = args

现在您可以访问life.py中的func1和data。当然,在尝试使用它们之前,您必须小心地在life.py中填充它们。


我认为这是最佳答案。 - undefined

2

我们的文件夹结构:

/myproject
  project_using_ptdraft/
    main.py
  ptdraft/
    __init__.py
    nib.py
    simulations/
      __init__.py
      life/
        __init__.py
        life.py

我理解的方式是采用以包为中心的视角。 包根目录是ptdraft,因为它是包含__init__.py的最高级别目录。

包内的所有文件都可以使用绝对路径(相对于包根目录)进行导入,例如在life.py中,我们只需简单地写:

import ptdraft.nib

然而,为了在包开发/测试目的下运行life.py,我们需要使用以下命令,而不是python life.py

cd /myproject
python -m ptdraft.simulations.life.life

请注意,此时我们不需要调整任何路径。
进一步的困惑出现在我们完成了 `ptdraft` 包之后,想要将其用于驱动脚本中,这个脚本必须位于 `ptdraft` 包文件夹外部,也就是 `project_using_ptdraft/main.py`,我们需要处理路径:
import sys
sys.path.append("/myproject")  # folder that contains ptdraft
import ptdraft
import ptdraft.simulations

使用python main.py来运行脚本,不会出现问题。

有用的链接:


2
使用pathlib.Path
import sys
from pathlib import Path
sys.path.append(str(Path(f"{__file__}").parent.parent))
import my_module

1
在移除了一些sys路径的hack之后,我认为添加以下内容可能会很有价值。

我的首选解决方案。

注意:这是一个框架挑战 - 不需要在代码中完成。

假设有一棵树,

project
└── pkg
    └── test.py

其中test.py包含

import sys, json; print(json.dumps(sys.path, indent=2)) 

只使用路径执行会包含包目录

python pkg/test.py
[
  "/project/pkg",
 ...
]

但使用模块参数会包括项目目录

python -m pkg.test
[
  "/project",
  ...
]

现在,所有的导入都可以是绝对路径,来自于项目目录。不需要进一步的欺骗手段。


1

与库一起工作。 创建一个名为nib的库,使用setup.py进行安装,让它驻留在site-packages中,您的问题就解决了。 您不必将所有内容都放在一个包中。将其分解成多个部分。


3
您的想法不错,但有些人可能希望将其模块与代码捆绑在一起 - 也许是为了使其具有可移植性,并且不需要在每次在不同计算机上运行时都将其模块放入 site-packages 文件夹中。 - Edward

1

我曾经遇到一个问题,需要导入一个Flask应用程序,其中还有一个需要导入单独文件夹中的文件的导入。这部分内容参考了 Remi的回答,但是假设我们有一个像这样的存储库:

.
└── service
    └── misc
        └── categories.csv
    └── test
        └── app_test.py
    app.py
    pipeline.py

然后,在从app.py文件中导入app对象之前,我们将目录更改为上一级,这样当我们导入app(它会导入pipeline.py)时,我们也可以读取其他文件,例如csv文件。

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)

os.chdir('../')
from app import app

在导入Flask应用程序后,您可以使用os.chdir('./test')以使您的工作目录不改变。

0

这是对我而言最简单有效的解决方案:

from ptdraft import nib

0
我创建了这个库来完成这个任务。

https://github.com/fx-kirin/add_parent_path

# Just add parent path
add_parent_path(1)

# Append to syspath and delete when the exist of with statement.
with add_parent_path(1):
   # Import modules in the parent path
   pass

它仍然需要解释。它的要点是什么? - Peter Mortensen

-1

虽然这违反了所有规则,但我仍然想提到这种可能性:

您可以先将文件从父目录复制到子目录。接下来导入它,然后删除已复制的文件:

例如在life.py中:

import os
import shutil

shutil.copy('../nib.py', '.')
import nib
os.remove('nib.py')

# now you can use it just fine:
nib.foo()

当nibs尝试使用相对导入/路径导入/读取其他文件时,可能会出现几个问题。

1
这是一个创意解决方案!虽然我可能不会使用它。 - onewhaleid
2
我强烈建议不要使用这种解决方案,而是将父目录也作为一个包或者只是将父目录附加到您的 sys.path 中(如其他答案中所建议的)。复制/移除文件会引入对文件位置的非常紧密的依赖关系,当未来更改项目时可能会被忽略(从而使脚本不够健壮)。此外,它还会防止文件被缓存,并且当然也会为所需的IO创建轻微的开销。 - Kim
2
我明白人们会对这篇文章进行负评,因为这确实是一种不好的实践方法,当我编写一个包或可重用脚本时,我永远不会使用它,但在某些情况下仍然值得尝试。例如,在运行交互式会话并且您只想在另一个位置使用脚本中定义的某些函数时。我认为,这些解决方案仍然可以在stackoverflow上提到是一种好的做法。 - JLT
我理解你的评论,@JLT,并且认为将其包含在StackOverflow中供参考可能是值得的。那么为什么不更新你的答案以反映你的推理,并提到一些相关的注意事项/陷阱呢? - Kim

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