导入不在 sys.path 文件的顶部

问题

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

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

然而,在某些情况下,我可能想要这样做:

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


import my_module

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

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

什么是实现与 sys.path修改的 PEP8一致性的最佳方法?

为什么

我有这个代码,因为我遵循 巨蟒漫游指南中给出的 项目结构

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

54061 次浏览

To comply with the pep8, you should include your project path to the python path in order to perform relative / absolute imports.

To do so, you can have a look at this answer: Permanently add a directory to PYTHONPATH

Often I have multiple files with tests in a subdirectory foo/tests of my project, while the modules I'm testing are in foo/src. To run the tests from foo/tests without import errors I create a file foo/tests/pathmagic.py that looks like this;

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

In every test file, I then use

import pathmagic  # noqa

as the first import. The "noqa" comment prevents pycodestyle/pep8 from complaining about an unused import.

There is another workaround.

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


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

If there are just a few imports, you can just ignore PEP8 on those import lines:

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

I've just struggled with a similar question, and I think I found a slightly nicer solution than the accepted answer.

Create a pathmagic module that does the actual sys.path manipulation, but make the change within a 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

Then, in your test files (or wherever you need this), you do:

import pathmagic


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

This way you don't get any complaints from flake8/pycodestyle, you don't need special comments, and the structure seems to make sense.

For extra neatness, consider actually reverting the path in the __exit__ block, though this may cause problems with lazy imports (if you put the module code outside of the context), so maybe not worth the trouble.


EDIT: Just saw a much simpler trick in an answer to a different question: add assert pathmagic under your imports to avoid the noqa comment.

Did you already try the following:

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

This problem already has several solutions that work, but in the case that you have a number of non-initial imports, and don't want to annotate each with # noqa: E402 (or use one of the other proposals), the following works for a whole block:

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


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

Given that the path to be added is relative to the script, this can be solved by using relative path.

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

Then do not need to use sys.path.insert() anymore. Lint stoped complaining.