从子模块导入Python包时避免使用pylint投诉

时间:2018-02-17 00:08:21

标签: python python-import git-submodules conventions pylint

背景

我有一个Python应用程序依赖于另一个包,它作为git子模块提供,产生类似于以下的目录结构:

foo/
    bar/
        bar/
            __init__.py
            eggs.py
        test/
        setup.py
    foo/
        __init__.py
        ham.py
    main.py

访问foo包很简单,因为main.py从顶级foo/目录执行;但bar包嵌套在另一个bar目录中,不能直接导入。

通过在sys.path开头修改main.py,这很容易解决:

import sys

# Or sys.path.append()
sys.path.insert(0, './bar')

from bar.eggs import Eggs
from foo.ham import Ham

(注意:此代码示例假定始终会从main.py调用foo/;如果情况不是这样,'.bar'可以替换为os.path.join(os.path.dirname(__file__), 'bar')虽然这显然更加笨拙。)

问题

不幸的是,pylint不喜欢这个解决方案。当代码工作时,linter认为sys.path修改是一个代码块,结束了“模块的顶部”并给出了一个不合需要的wrong-import-position警告:

C: 6, 0: Import "from bar.eggs import Eggs" should be placed at the top of the module (wrong-import-position)
C: 7, 0: Import "from foo.ham import Ham" should be placed at the top of the module (wrong-import-position)

类似问题

Adding a path to sys.path in python and pylint

此提问者的问题是pylint无法正确解析导入。这个问题的唯一答案表明增加了pylint的内部路径;这没有什么可以避免关于交错sys.path修改的投诉。

2 个答案:

答案 0 :(得分:6)

配置pylint

wrong-import-position中禁用.pylintrc检查程序是最简单的解决方案,但会抛弃有效的警告。

更好的解决方案是告诉pylint忽略这些导入的wrong-import-position内联。误报导入可以嵌套在启用禁用块中,而不会丢失其他地方的任何覆盖:

import sys

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

#pylint: disable=wrong-import-position

from bar.eggs import Eggs
from foo.ham import Ham

#pylint: enable=wrong-import-position

Ham()

# Still caught
import something_else

但是,如果wrong-import-order.pylintrc被禁用,则确实会出现轻微的下滑感。

避免修改sys.path

有时不需要的linting警告源于错误地开始使用问题。我已经想出了一些方法来避免首先修改sys.path,尽管它们不适用于我自己的情况。

也许最直接的方法是修改PYTHONPATH以包含子模块目录。但是,每次调用应用程序或在系统/用户级别修改应用程序时都必须指定这一点,这可能会损害其他进程。该变量可以在包装shell或批处理脚本中设置,但这需要进一步的环境假设或限制对Python调用的更改。

更现代,更少麻烦的模拟是在虚拟环境中安装应用程序,只需将子模块路径添加到虚拟环境中。

到达更远的地方,如果子模块包含setuptools setup.py,则可以简单地安装它,完全避免路径自定义。这可以通过将出版物维护到诸如pypi(非专有包的入门者)之类的存储库,或者通过利用/滥用pip install -e直接安装子模块包或从其存储库来安装。虚拟环境再一次避免了潜在的跨应用程序冲突和权限问题,从而使此解决方案更加简单。

如果目标操作系统集可以限制为具有强大符号链接支持的那些(实际上这排除了所有Windows至少10个),子模块可以链接到绕过包装目录并直接将目标包放入工作中目录:

foo/
    bar/ --> bar_src/bar
    bar_src/
        bar/
            __init__.py
            eggs.py
        test/
        setup.py
    foo/
        __init__.py
        ham.py
    main.py

这有限制了应用程序的潜在用户和填充foo目录的混乱,但在某些情况下可能是一个可接受的解决方案。

答案 1 :(得分:1)

硬编码位置

此设置的问题在于它对文件的位置进行了非常特定的假设。特别是,它硬编码一个位置到另一个包。

在您的情况下,您将其硬编码为相对路径。这另外要求最终用户具有非常特定的当前目录。如果您是最终用户,这很烦人。如果我有一个文件我想用作代码的输入,我应该可以将当前目录作为我的用户主目录路径(Linux中为~,Windows中为%USERPRPOFILE%)并传入使用脚本本身的绝对路径时,我的文件的相对路径。 (例如,python /path/to/your/script ./myinput.txt。)像这样的硬编码位置使得无法做到。我还注意到您的bar目录包含setup.py,这意味着它是一个独立的包。精彩。如果我想再次运行某个版本的安装main.py怎么办?同样,对于脚本执行sys.path的修改,这是不可能的。

您应该在代码中硬编码的位置是资源的位置,这些资源将直接直接分发,代码始终相同的位置,就像recipes.dat文件旁边有eggs.py一样。在这种情况下,路径应该相对于脚本的(或其他语言中的二进制文件)当前位置。 (例如,RECIPES_PATH = os.path.join(os.path.dirname(__name__), 'recipes.dat')。)当你有一个单独的包时,它可能在你的main.py脚本所期望的其他地方。

让Python做它的工作

查找和加载包是Python的基本功能。 让它做到这一点。当你遇到无法立即找到它的情况时(因为你的代码没有安装在任何地方),使用标准与他们合作的机制。

PYTHONPATH环境变量可能是处理它的最简单方法。这很容易。您只需要一个配套脚本来设置命令行环境:

setupenv.sh

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # See https://stackoverflow.com/a/246128/1394393

if [ -n "$PYTHONPATH" ]; then
    PYTHONPATH=$PYTHONPATH:
fi
PYTHONPATH=$PYTHONPATH${DIR%%/}/bar

然后:

$ source setupenv.sh
$ python ./main.py

(在Windows批处理/ cmd文件中执行此操作也同样简单。)

好的,当您积极开发代码时,每次启动终端时都必须设置环境,这是一个很小的。但它并没有那么糟糕。我在自己的项目中这样做,这是我早上做的事情,在我推出新终端之前不要再考虑了。 (我的脚本设置了更多:激活虚拟环境,为某些本机二进制文件设置PATH。)对于项目来说,它更加清晰。

你可能会说,“好吧,我们仍然在sh文件中对位置进行硬编码。”是的我们是。但是这个脚本是存储库的一部分。请注意,我使用的路径是相对于脚本本身;那是因为我知道代码库是如何构建的。当他们在命令行工作时,我不知道用户的当前目录,我当然不知道将在哪里分发main.py。也许它最终将在最终目的地的自己的包装中。无论如何,知道其他软件包所在的位置并不是该脚本的工作。 是此setupenv.sh脚本的作业,在此存储库中