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

Posted

技术标签:

【中文标题】PEP8 - 使用 sys.path 导入不在文件顶部【英文标题】:PEP8 – import not at top of file with sys.path 【发布时间】:2016-08-18 02:23:34 【问题描述】:

问题

PEP8 有一条关于将导入放在文件顶部的规则:

导入总是放在文件的顶部,就在任何模块 cmets 和文档字符串之后,模块全局变量和常量之前。

但是,在某些情况下,我可能想做一些类似的事情:

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

import my_module

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

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

通过sys.path 修改实现 PEP8 合规性的最佳方法是什么?

为什么

我有这个代码是因为我关注The Hitchhiker's Guide to Python 中给出的the project structure。

该指南建议我有一个 my_module 文件夹,与 tests 文件夹分开,两者都在同一个目录中。如果我想从tests访问my_module,我想我需要将..添加到sys.path

【问题讨论】:

为什么不写一个setup.py 并实际安装 my_module 进行测试? 因为这样不太方便。我想我可以,但我宁愿不这样做。 为谁?如果您想在任何地方实际使用这个项目,那么启动和运行它是最简单的方法。 来自PEP8:“但是,知道什么时候应该不一致——有时风格指南的建议并不适用。如有疑问,请做出最佳判断。”。有时您必须违反 PEP8 合规性,这没关系。 @jonrsharpe 这是我将分享的未来事情的好习惯。 (我确实明白你的意思,在这种情况下我可以使用 setup.py)。我会记住这一点的。 【参考方案1】:

如果只有几个导入,您可以忽略那些 import 行上的 PEP8:

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

【讨论】:

我更喜欢更明确,例如指定违反规则的# noqa: E402。 (source) @MaxGoodridge 确实!编辑响应以添加规则。 如果您从同一路径有多个导入,我将# noqa: E402 放在sys.path.insert... 行上会更短【参考方案2】:

通常我在项目的子目录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 抱怨未使用的导入。

【讨论】:

这很酷,但这仍然存在“已导入但未使用 [F401]”的问题。 我想在那个 pathmagic 模块中创建一个虚拟函数并从测试模块中调用它可以解决这个问题,但我希望有更干净的东西...... @Chung-YenHung 请记住,pycodestyle/pep8 警告是建议性的,而不是语法错误或异常。您可以选择忽略它们。我通过在导入后添加“noqa”评论来更新我的答案。 这样做的一个巨大缺点是将您的测试变成一个包,以便pathmagic 可以导入。大多数 python 测试运行者假设您的测试是不在sys.path 上的文件集合,并且更改可能会导致问题,请参阅pytest 如何处理这些问题docs.pytest.org/en/latest/… @Meitham 虽然我的回答提到了我当时是如何使用它来运行测试的,但问题不在于运行测试。 (我已经转移到 pytest 进行测试。)在其他情况下,这种机制仍然有用。【参考方案3】:

还有另一种解决方法。

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

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

【讨论】:

我坚信这行不通。原因是,可能在“...所有您的导入...”下导入了一些模块,这可能需要先设置 PYTHONPATH。 @darkdefender27 这个想法是将所有需要 PYTHONPATH 的导入放在 try 正文和其他所有内容(不依赖于它)上面。 或更简单:if 1: import module【参考方案4】:

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

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

if True:  # noqa: E402
    import my_module_1
    import my_module_2
    ...

【讨论】:

【参考方案5】:

我刚刚在一个类似的问题上苦苦挣扎,我想我找到了一个比接受的答案更好的解决方案。

创建一个 pathmagic 模块来执行实际的 sys.path 操作,但在 context manager 内进行更改:

"""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 的任何抱怨,你不需要特殊的 cmets,而且结构似乎很有意义。

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


编辑:刚刚在answer to a different question 中看到了一个更简单的技巧:在您的导入下添加assert pathmagic 以避免noqa 注释。

【讨论】:

所有这一切真正完成的是摆脱了特殊注释的下一个,代价是需要使用上下文管理器——在我看来,这是一个有点模糊的权衡。至于__exit__ 块中的清理,要真正正确地进行清理,只需删除添加的路径(如果它仍然存在),因为将整个先前的值恢复到输入上下文时的值也会撤消任何其他出于某种原因,其他代码(在同一上下文中执行)可能对其进行的更改。 @martineau 确实,这是一个品味问题。我可能对特殊的 cmets 有一点偏见,因为我当前的代码库对于各个团队正在使用的无数静态分析工具和编辑器来说包含太多的 cmets。也同意你的第二点。 如果pathmagic.context() 可以被调用任意次数(例如,如果运行所有测试),则会出现问题。还原也是有问题的:以后的任何导入(例如按需完成)都可能失败。【参考方案6】:

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

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')

【讨论】:

【参考方案7】:

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

为此,您可以看看这个答案:Permanently add a directory to PYTHONPATH

【讨论】:

我不想让我的所有 Python 脚本都可以全局访问这个目录,因为这可能会导致冲突。 您可以使用docs.python.org/3/library/pkgutil.html 包来使用命名空间。如果您认为您的解决方案是最好的,那么您没有义务遵循 pep8。 Pep8 只是建议和最佳实践,并不意味着您必须随时随地遵守每条规则。 PEP 8 实际上说 A foolish consistency is the hobgoblin of little minds.【参考方案8】:

由于要添加的路径是相对于脚本的,所以可以使用相对路径来解决。

from ..mypath import mymodule 
from ..mypath.mysubfolder import anothermodule

那么就不需要再使用sys.path.insert()了。 Lint 不再抱怨了。

【讨论】:

以上是关于PEP8 - 使用 sys.path 导入不在文件顶部的主要内容,如果未能解决你的问题,请参考以下文章

sys.path.insert

如何刷新 sys.path?

为啥我的脚本目录不在 Python sys.path 中?

python 怎么引入上上级目录的文件

ImportError:无法导入设置(是不是在 sys.path 上?设置文件中是不是存在导入错误?):没有名为 setting 的模块

python 怎么引用其他文件的类