Python从父目录导入模块

22

我有以下内容:

         ModuleFolder
              |
              |-->. ModuleFile.py .
              |
              '-->. TestsFolder .
                         |
                         '---> UnitTest1.py

我试图从父目录导入内容。在这种情况下,我正在尝试从测试文件夹中运行"UnitTest1.py"文件,并从其上方的目录(文件"ModuleFile.py")进行导入。

  • 我知道已经有很多关于这个问题的答案了。SO Question1SO Question2每一个其他的SO问题。但我找不到像使用 "../" 这样的相对路径导入方式而不是显式路径。
  • 我知道自Python 2.5起,他们支持“相对导入”,并且有文档提到了使用from .. import *, 但我特别想做一个 import MyModuleName,这样可以更明确地在unittest中避免名称混淆和冲突。

我正在执行以下操作(并且它对我有效):

sys.path.append("../")

然后从父目录中导入我需要的内容。

  • 是的,在父目录中有__init__.py文件。
  • 不,我的父路径不是Python路径或环境变量的一部分。
  • 为什么我不只是将父路径添加到sys.path中呢?因为它是相对的。如果我正在运行/home/workspace/MyModule/unittests/并且我的模块在/home/workspace/MyModule/下,我假设将/home/workspace/MyModule/添加到路径中不一定是正确的,如果一个同事在他自己的目录/home/documents/MyModule下运行它。

我的问题:

这符合Python规范吗?如果不是,有什么问题。有更好的方法吗?还是这真的是RTFM时刻,其中答案在我已经查看的7+ SO问题中的一个中(我看到这些都建议使用显式路径而不是我采取的相对路径方法)。

其他有用的信息:

  • Python 2.6
  • 在Linux工作,但也可以轻松地切换到Windows。

4
最好将你的代码作为“包”开始使用,并将所有内容导入为此。此时,相对导入是不相关的。 - Asclepius
6个回答

29

不要从测试文件夹中运行测试。请从项目的根目录(即模块文件夹)运行测试。您很少需要干预sys.pathPYTHONPATH,当您这样做时,您可能会在未来影响其他库的稳定性,或者为您的用户带来麻烦。

python -m TestsFolder.UnitTest1
如果您使用像 py.test 这样的测试运行器,您只需从您代码库的根目录运行 py.test 它就会为您找到测试。 (假设您将测试命名为类似于 test_unit1.py 的命名方式。 您当前的命名方案有点不太正规。 ;))

2
这在 "Python老手" 中是否被理解,即测试很少从测试目录运行,通常应在模块级别调用测试?从父目录运行测试似乎是反直觉的,因为您完全可以转到测试目录并键入 python test.py 来查看结果 - 我的意思是所有其他非测试 * .py文件都是这样做的。 - Justin Carroll
4
不是这样的。使用“python foo/bar.py”运行可导入文件是不明智的,因为它会将“foo/”添加到“sys.path”,而这很少是您想要的。请使用“-m”运行入口点,并使用测试运行器运行测试。我很少比我的项目根目录更深入使用“cd”。 - Eevee
1
我总是忘记使用python -m。需要在我的小隔间墙上贴一个标签或其他东西,因为忘记它已经让我多次损失了几个小时的生产力。+1 - Jacklynn

5

最好在 sys.path 的开头插入相对路径,如下所示:

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

不确定为什么你被投票否决了,但我真的很感激你的努力!为什么把它放在开头会更好呢?这不是我第一次看到这个建议了,但我从来没有理解过为什么... - Justin Carroll
1
@JustinCarroll:你把路径放在开头,以确保即使出现路径冲突,你的代码也能被调用。 - Laurent LAPORTE

2

将测试文件夹变成一个模块(添加__init__.py文件)。
然后,您可以使用运行命令python -m tests.test_name或在项目主目录中使用以下Makefile

TEST_FILES = $(wildcard tests/test_*.py)
TESTS = $(subst .py,,$(subst /,.,$(TEST_FILES)))
all.PHONY: test
test:
    @- $(foreach TEST,$(TESTS), \
        echo === Running test: $(TEST); \
        python -m $(TEST); \
        )

2

您也可以创建要导入的模块路径的符号链接,然后使用该符号链接进行导入。在python dist-packages中创建一个符号链接。

创建符号链接的方法如下:

ln -s "/path/to/ModuleFolder" "/path/to/python/dist/packages/module_symlink_name"

在脚本中导入模块的方法如下:

from module_symlink_name import ModuleFile

无需导出Python路径或修改sys路径。

2

我的建议是:

不要试图聪明,做你应该做的事情。也就是说,确保你的模块和包在 Python 路径中的某个位置。

最简单的方法是在你执行脚本的 shell 中设置环境变量 PYTHONPATH:

$ export PYTHONPATH=/the/directory/where/your/modules/and/packages/are
$ cd /the/directory/where/your/unit/tests/are
$ python test1.py

7
我对此的问题是,当其他人将其下载到其环境中运行时,他们会在测试中遇到错误(误报),因为他们没有正确设置环境变量。这是他们的问题吗?最好还是按照惯例去做?我很惊讶居然没有比“每个想要运行单元测试的用户必须先设置他们的Python环境变量否则他们无法运行它”更好的解决方案……我对此太过热心吗?(感谢快速回复) - Justin Carroll
我认为遵循惯例是最好的。确保你的测试能够快速失败。通常你应该将所有导入放在单元测试模块文件的顶部。这样一来,单元测试会立即出现 "ImportError"。教会你的同事们这个错误的含义以及如何解决它(例如在 Windows 中执行 export PYTHONPATH=... 或者 SET PYTHONPATH=...)。 - codeape
最简单的方法是从包根目录中运行 python - Eevee
添加 sys.path.append("../") 有什么问题吗?其中的问题可能会导致代码出错。我完全明白这是“不好的形式”和违反惯例,有更好、更规范的解决方案来解决这个问题——我喜欢这里和 @Eevee 提出的建议,但现在我对实际影响感兴趣,以便我可以向他人提供关于利弊的建议(超出违反惯例的范畴)。 - Justin Carroll
确定一下:.. 究竟在哪里?如果我没有从与你完全相同的位置运行 Python(也许我已经安装了你的库!),它可能会出现在任何地方并引入任何东西。 - Eevee

0

这总是很令人困惑的。相对导入是相对于模块结构的。要测试相对导入,您需要在主文件中导入您的模块(从模块外部)。让我试着展示一个可行的情况:

$ python use_module.py
Hello World!

或者你可以将它作为可执行模块,并使用python -m命令来运行它(始终从模块外部运行)

$ python -m module.child_a
Hello World!

结构

test_module.py
module
├── __init__.py [can be empty]
├── child_a
   ├── __init__.py
   ├── __main__.py
│── child_b
   ├── foo.py

test_module.py

from module.child_a import message
print(message)

foo.py

phrase = "Hello World"
mark = '!'

child_a/__init__.py

from .__main__ import message

child_a/__main__.py

from .. child_b.foo import phrase, mark

message = phrase + mark

if __name__ == "__main__":
    #this block is run when executing `python -m module.etl`

    print(message)

注意:如果您在child_a中添加另一个文件,例如child_a/myscript.py,您仍然可以使用python -m module.sub_a.myscript执行它(始终从模块外部执行)。


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