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

问题描述 投票:2回答:2

背景

我有一个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修改的投诉。

python python-import git-submodules conventions pylint
2个回答
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

有时不需要的掉毛警告源于错误地开始出现问题。我想出了一些方法来避免首先修改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
投票

硬编码位置

这种设置的问题在于它对文件的位置做出了非常具体的假设。特别是,它将位置硬编码到另一个包。

在您的情况下,您将其硬编码为相对路径。这另外要求最终用户具有非常特定的当前目录。如果您是最终用户,这很烦人。如果我有一个文件我想用作你的代码的输入,我应该能够将我当前的目录作为我的用户主目录路径(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脚本的工作,在此存储库中。

© www.soinside.com 2019 - 2024. All rights reserved.