PEP8 –使用sys.path不在文件顶部导入


75

问题

PEP8具有将导入置于文件顶部的规则:

导入总是放在文件的顶部,紧随任何模块注释和文档字符串之后,以及模块全局变量和常量之前。

但是,在某些情况下,我可能想要执行以下操作:

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

import my_module

在这种情况下,pep8命令行实用程序会标记我的代码:

E402模块级别导入不在文件顶部

通过sys.path修改达到PEP8的最佳方法是什么?

为什么

我有这段代码,因为我遵循是《The Hitchhiker's Guide to Python》中给出的项目结构

该指南建议我有一个与my_module文件夹分开的tests文件夹,这两个文件夹都位于同一目录中。如果我要访问my_moduletests,我想我需要添加..sys.path


您为什么不编写setup.py并实际安装 my_module进行测试?
jonrsharpe

因为那不太方便。我想可以,但我不愿意。
卢克·泰勒

为了谁?如果您想在任何地方实际使用此项目,那么它是最简单的启动和运行方法。
jonrsharpe

9
PEP8中:“但是,知道何时不一致-有时样式指南建议不适用。如有疑问,请运用最佳判断。” 有时候您必须违反PEP8规范,那是可以的。
SethMMorton '16

1
@jonrsharpe这是养成我将要分享的未来事物的好习惯。(不过,我确实知道您的意思,在这种情况下,我可以使用setup.py)。我会记住这一点。
卢克·泰勒

Answers:


62

通常foo/tests,我的项目的子目录中有多个带有测试的文件,而我正在测试的模块位于中foo/src。为了从foo/tests没有导入错误的情况下运行测试,我创建了一个foo/tests/pathmagic.py看起来像这样的文件;

"""Path hack to make tests work."""

import os
import sys

bp = os.path.dirname(os.path.realpath('.')).split(os.sep)
modpath = os.sep.join(bp + ['src'])
sys.path.insert(0, modpath)

然后在每个测试文件中,使用

import pathmagic  # noqa

作为第一个导入。“ noqa”注释可防止pycodestyle/pep8抱怨未使用的导入。


2
这很酷,但是仍然存在“导入但未使用[F401]”的问题。
仲颜红

2
我猜想在该pathmagic模块中创建一个虚拟函数并从测试模块中调用它可以解决该问题,但我希望有一些更清洁的方法……
Alexis.Rolland

3
@ Chung-YenHung请记住,pycodestyle / pep8警告是建议性的,而不是语法错误或异常。您可以选择忽略它们。我已在导入后添加“ noqa”注释来更新我的答案。
罗兰·史密斯

这样做的一个巨大缺点是将您的测试打包成一个包,因此pathmagic可以导入。大多数python测试运行程序都假设您的测试是不存在的文件的集合sys.path,而更改会导致问题,请查看如何pytest解决这些问题docs.pytest.org/en/latest/…–
Meitham

1
@Meitham尽管我的回答提到了我当时是如何使用它来运行测试的,但问题不在于运行测试。(此后,我已将其转移到pytest进行测试。)在其他情况下,此机制仍然有用。
罗兰·史密斯

82

如果只有少量导入,则可以在这些import行上忽略PEP8 :

import sys
sys.path.insert("..", 0)
import my_module  # noqa: E402

6
我希望更明确一些,# noqa: E402例如指定违反规则。(来源
Max Goodridge

确实是@MaxGoodridge!编辑响应以添加规则。
阿斯托加

10

还有另一种解决方法。

import sys
... all your other imports...

sys.path.insert("..", 0)
try:
    import my_module
except:
    raise

1
我坚信这是行不通的。原因是,可能在“ ...所有导入...”下导入了一些模块,这可能需要首先设置PYTHONPATH。
darkdefender27年

2
@ darkdefender27的想法是将所有需要PYTHONPATH的导入放到try主体内部,并将其他所有不依赖于此的导入放到上面。
阿斯托加

或更简单:if 1: import module
stason

4

我一直在努力解决类似的问题,而且我认为我找到了比可接受答案更好的解决方案。

创建一个pathmagic执行实际sys.path操作的模块,但在上下文管理器中进行更改:

"""Path hack to make tests work."""

import os
import sys

class context:
    def __enter__(self):
        bp = os.path.dirname(os.path.realpath('.')).split(os.sep)
        modpath = os.sep.join(bp + ['src'])
        sys.path.insert(0, modpath)

    def __exit__(self, *args):
        pass

然后,在测试文件中(或在需要的地方)执行以下操作:

import pathmagic

with pathmagic.context():
    import my_module
    # ...

这样,您就不会从flake8 / pycodestyle收到任何投诉,也不需要特殊的注释,并且该结构似乎很有意义。

为了使内容更加整洁,请考虑实际还原__exit__块中的路径,尽管这可能会导致延迟导入(如果将模块代码放在上下文之外)会引起问题,因此可能不值得这样做。


编辑:刚刚在回答另一个问题时看到了一个简单得多的技巧:assert pathmagic在您的导入内容下添加以避免noqa注释。


所有这些真正完成的工作都摆脱了下一个特别评论,以牺牲了使用上下文管理器为代价,在我看来这是一个模糊的折衷。至于清除__exit__块中的内容,要真正正确地执行操作,只需删除添加的路径(如果它仍然存在),因为将整个先前的值恢复为输入上下文时的值,还会撤消可能由于某种原因,其他代码(在相同上下文中执行)对它进行了处理。
martineau

@martineau确实,这是一个品味问题。我可能对特殊注释有些偏颇,因为我当前的代码库中包含了太多的注释,用于各个团队正在使用的大量静态分析工具和编辑器。也同意你的第二点。
itsadok '17

有问题,是否pathmagic.context()可以调用任意次(例如,如果运行所有测试)。还原也有问题:以后的任何导入(例如按需完成)都可能失败。
ivan_pozdeev

4

您是否已经尝试过以下方法:

import sys
from importlib import import_module

sys.path.insert("..", 0)

# import module
my_mod = import_module('my_module')

# get method or function from my_mod
my_method = getattr(my_mod , 'my_method')

2

为了符合pep8,您应该将项目路径包含在python路径中,以便执行相对/绝对导入。

为此,您可以看一下以下答案:将目录永久添加到PYTHONPATH


3
我不想使该目录可被所有Python脚本全局访问,因为这可能会导致冲突。
卢克·泰勒

2
您可以使用docs.python.org/3/library/pkgutil.html包使用名称空间。如果您认为您的解决方案是最好的,则您没有义务遵循pep8。Pep8仅是建议和最佳实践,并不意味着您必须随时随地遵循每条规则。
皮埃尔·巴雷


0

这个问题已经有几种解决方案可以工作,但是如果您有许多非初始导入,并且不想用# noqa: E402(或使用其他建议之一来注释)每个注释,则以下内容适用于整个块:

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

if True:  # noqa: E402
    import my_module_1
    import my_module_2
    ...
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.