正确管理 Python 包

不要成为 Python 包管理困境的受害者。
259 位读者喜欢这篇文章。
Using Python to find corrupted images

Jason van Gumster。CC BY-SA 4.0

Python 包索引 (PyPI) 索引了大量的库和应用程序,涵盖了所有可以想象到的用例。然而,当涉及到安装和使用这些包时,新手经常会遇到权限缺失、库依赖不兼容以及安装以令人惊讶的方式崩溃等问题。

Python 之禅指出:“应该有一种——最好只有一种——显而易见的方法来做到这一点。” 然而,在安装 Python 包时,情况并非总是如此。但是,有一些工具和方法可以被认为是最佳实践。了解这些可以帮助您在正确的情况下选择正确的工具。

系统级安装应用程序

pip 是 Python 世界中事实上的包管理器。它可以从许多来源安装包,但 PyPI 是它使用的主要包来源。在安装包时,pip 将首先解析依赖项,检查它们是否已安装在系统上,如果未安装,则安装它们。一旦满足所有依赖项,它将继续安装请求的包。默认情况下,这一切都在全局范围内发生,将所有内容安装到机器上的单个操作系统相关位置。

Python 3.7 在 Arch Linux 系统上查找包的以下位置

$ python3.7 -c "import sys; print('\n'.join(sys.path))"

/usr/lib/python37.zip
/usr/lib/python3.7
/usr/lib/python3.7/lib-dynload
/usr/lib/python3.7/site-packages

全局安装的一个问题是,对于给定的 Python 解释器,一次只能安装一个版本的包。当一个包是多个库或应用程序的依赖项,但它们需要此依赖项的不同版本时,这可能会导致问题。即使事情看起来运行良好,升级依赖项(即使在安装另一个包时意外升级)也可能在将来破坏这些应用程序或库。

另一个潜在的问题是,大多数类 Unix 发行版都使用内置包管理器(dnfaptpacmanbrew 等)来管理 Python 包,并且其中一些工具安装到非用户可写的位置。

$ python3.7 -m pip install pytest
Collecting pytest
Downloading...
[...]
Installing collected packages: atomicwrites, pluggy, py, more-itertools, pytest
Could not install packages due to an EnvironmentError: [Error 13] Permission denied:
'/usr/lib/python3.7/site-packages/site-packages/atomicwrites-x.y.z.dist-info'
Consider using '--user' option or check the permissions.
$

这会失败,因为我们以非 root 用户身份运行 pip install,并且我们没有写入 site-packages 目录的权限。

从技术上讲,您可以通过以 root 用户(使用 sudo 命令)或管理员用户身份运行 pip 来解决此问题。但是,一个问题是我们刚刚将一堆 Python 包安装到 Linux 发行版的包管理器拥有的位置,这使得其内部数据库和安装不一致。每当我们尝试使用包管理器安装、升级或删除这些依赖项中的任何一个时,都可能会导致问题。

例如,让我们尝试再次安装 pytest,但这次使用我的系统包管理器 pacman

$ sudo pacman -S community/python-pytest
resolving dependencies...
looking for conflicting packages...
[...]
python-py: /usr/lib/site-packages/py/_pycache_/_metainfo.cpython-37.pyc exists in filesystem
python-py: /usr/lib/site-packages/py/_pycache_/_builtin.cpython-37.pyc exists in filesystem
python-py: /usr/lib/site-packages/py/_pycache_/_error.cpython-37.pyc exists in filesystem

另一个潜在的问题是,操作系统可以使用 Python 来处理系统工具,并且我们很容易通过在系统包管理器外部修改 Python 包来破坏这些工具。这可能会导致系统无法运行,只能通过从备份还原或完全重新安装来修复。

sudo pip install:一个坏主意

还有另一个原因说明为什么以 root 用户身份运行 pip install 是一个坏主意。为了解释这一点,我们首先必须了解 Python 库和应用程序是如何打包的。

今天,大多数 Python 库和应用程序都使用 setuptools 作为其构建系统。setuptools 在项目根目录中需要一个 setup.py 文件,该文件描述了包元数据,并且可以包含任意 Python 代码来自定义构建过程。当从源发行版安装包时,将执行此文件以执行安装并执行诸如检查系统、构建包等任务。

使用 root 权限执行 setup.py 意味着我们可以有效地向恶意代码或错误开放系统。这种情况比您想象的更有可能发生。例如,在 2017 年,有几个 包被上传到 PyPI,其名称类似于流行的 Python 库。上传的代码收集了系统和用户信息,并将其上传到远程服务器。这些包很快就被撤下。然而,由于任何人都可以将包上传到 PyPI,并且没有审查过程来确保代码不会造成任何危害,因此这种“拼写错误抢注”事件随时可能发生。

Python 软件基金会 (PSF) 最近宣布,它将赞助工作以 提高 PyPI 的安全性。这应该会使执行诸如“pytosquatting”之类的攻击变得更加困难,并有望在未来减少这个问题。

除了安全问题之外,sudo pip install 也无法解决所有依赖性问题:您仍然只能安装任何给定库的单个版本,这意味着仍然很容易通过这种方式破坏应用程序。

让我们看看一些更好的替代方案。

操作系统包管理器

我们选择的操作系统上使用的“原生”包管理器很可能也可以安装 Python 包。问题是:我们应该使用 pip,还是 aptdnfpacman 等?

答案是:视情况而定

pip 通常用于直接从 PyPI 安装包,而 Python 包作者通常在那里上传他们的包。但是,大多数包维护者不会使用 PyPI,而是从作者创建的源发行版 (sdist) 或版本控制系统(例如 GitHub)中获取源代码,根据需要应用补丁,并测试和发布其各自平台的包。与 PyPI 发行模型相比,这有利有弊

  • 由原生包管理器维护的软件通常更稳定,并且在给定的平台上通常运行得更好(尽管情况可能并非总是如此)。
  • 这也意味着打包和测试上游 Python 代码需要额外的工作
    1. 包的选择通常比 PyPI 提供的要少得多。
    2. 更新速度较慢,并且包管理器通常会发布较旧的版本。

如果我们想要使用的包可用,并且我们不介意稍微旧的版本,那么包管理器提供了一种方便且安全的安装 Python 包的方法。而且,由于这些包是系统范围安装的,因此系统上的所有用户都可以使用它们。这也意味着只有当我们拥有在系统上安装包所需的权限时,我们才能使用它们。

如果我们想使用包管理器选择中不可用或版本太旧的东西,或者我们根本没有安装包的必要权限,我们可以改用 pip

用户方案安装

pip 支持 Python 2.6 中引入的“用户方案”模式。这允许将包安装到用户拥有的位置。在 Linux 上,这通常是 ~/.local。将 ~/.local/bin/ 放入我们的 PATH 中将使我们可以轻松地使用 Python 工具和脚本,并在没有 root 权限的情况下管理它们。

$ python3.7 -m pip install --user black
Collecting black
 Using cached
[...]
Installing collected packages: click, toml, black
 The scripts black and blackd are installed in '/home/tux/.local/bin' which is not on PATH.
 Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed black-x.y click-x.y toml-x.y.z
$

但是,如果我们需要同一包的不同版本,此解决方案无法解决问题。

进入虚拟环境

虚拟环境提供独立的 Python 包安装,它们可以在同一系统上独立共存。这提供了与用户方案安装相同的好处,但它也允许创建独立的 Python 安装,其中应用程序不与任何其他应用程序共享依赖项。Virtualenv 创建一个目录,其中包含独立的 Python 安装,包括 Python 二进制文件和用于包管理的基本工具:setuptoolspipwheel

创建虚拟环境

virtualenv 是一个第三方包,但 Python 3.3 将 venv 包添加到标准库中。因此,我们无需安装任何东西即可在现代版本的 Python 中使用虚拟环境。我们可以简单地使用 python3.7 -m venv <env_name> 来创建一个新的虚拟环境。

创建新的虚拟环境后,我们必须通过在新建环境的 bin 目录中 sourcing activate 脚本来激活它。激活脚本创建一个新的子 shell,并将 bin 目录添加到 PATH 环境变量,使我们能够从此位置运行二进制文件和脚本。这意味着此子 shell 将使用在此位置安装的 pythonpip 或任何其他工具,而不是全局安装在系统上的工具。

$ python3.7 -m venv test-env
$ . ./test-env/bin/activate
(test-env) $

此后,我们执行的任何命令都将使用虚拟环境内的 Python 安装。让我们安装一些包。

(test-env)$ python3.7 -m pip install --user black
Collecting black
 Using cached
[...]
Installing collected packages: click, toml, black
Successfully installed black-x.y click-x.y toml-x.y.z
(test-env) $

我们可以在虚拟环境中使用 black,而无需手动更改环境变量,如 PATHPYTHONPATH

(test-env) $ black --version
black, version x.y
(test-env) $ which black
/home/tux/test-env/bin/black
(test-env) $

当我们完成虚拟环境后,我们可以简单地使用 deactivate 函数来停用它。

(test-env) $ deactivate
$ 

虚拟环境也可以在不使用激活脚本的情况下使用。安装在 venv 中的脚本将重写其 shebang 行,以使用虚拟环境内的 Python 解释器。这样,我们可以使用脚本的完整路径从系统上的任何位置执行脚本。

(test-env) $ head /home/tux/test-env/bin/black
#!/home/tux/test-env/bin/python3.7

# -*- coding: utf-8 -*-
import re
import sys

from black import main

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
(test-env) $ 

我们可以简单地从系统上的任何位置运行 ~/test-env/bin/black,它就可以正常工作。

将某些常用的虚拟环境添加到 PATH 环境变量可能会很有用,这样我们就可以快速轻松地使用其中的脚本,而无需键入完整路径

export PATH=$PATH:~/test-env/bin

现在,当我们执行 black 时,它将从虚拟环境中拾取(除非它出现在 PATH 中更早的位置)。将此行添加到 shell 的初始化文件(例如 ~/.bashrc),使其在所有新 shell 中自动设置。

虚拟环境非常常用于 Python 开发,因为每个项目都有自己的环境,可以在其中安装所有库依赖项,而不会干扰系统安装。

我建议查看 virtualenvwrapper 项目,它可以帮助简化常见的基于 virtualenv 的工作流程。

Conda 怎么样?

Conda 是一种包管理工具,可以安装 Anaconda 在 repo.continuum.io 存储库上提供的包。它已经变得非常流行,尤其是在数据科学领域。它提供了一种简单的方法来创建和管理环境并在其中安装包。与 pip 相比,一个缺点是包的选择少得多。

成功进行包管理的秘诀

  • 永远不要运行 sudo pip install
  • 如果您想让机器的所有用户都可以使用某个包,您拥有正确的权限,并且该包可用,那么请使用您发行版的包管理器(aptyumpacmanbrew 等)。
  • 如果您没有 root 权限,或者操作系统包管理器没有您需要的包,请使用 pip install --user 并将用户安装目录添加到 PATH 环境变量。
  • 如果您希望同一库的多个版本共存,进行 Python 开发,或者只是为了隔离任何其他原因的依赖项,请使用虚拟环境。

本文最初于 2019 年 4 月发布,并由编辑更新。

标签
User profile image.
László Kiss Kollár 是彭博社的高级软件工程师,他在 Python 基础设施团队工作。他拥有近 15 年的行业经验,并参与过各种项目,例如优化向量处理器代码、创建分布式 JVM 以及生成自动生成的新闻内容。

11 条评论

感谢这篇文章,它早就应该出现了。我长期以来一直在 Ubuntu 中与系统库和我的库作斗争。这是一场绝对的噩梦,似乎没有人能解释清楚。

但是我想说 pipenv 让这一切对我来说容易得多。
这是一篇关于如何使用 pipenv 的好文章:https://realpython.com/pipenv-guide/

回复 作者:lkollar

非常感谢您没有认可 pipenv 这个垃圾堆

Python 3.7 现在支持这个特性真是太棒了。我个人仍然更喜欢 miniconda,因为它允许我安装一些软件包而无需 make, gcc,...
此外,conda 也自带 pip,因此你可以安装不在 conda 仓库中的软件包,从而兼得两者之长。

嗨,

在 SOURCEdefender[2] 的开发过程中,我们使用了 pyenv[1],因为它使我们能够在 Linux、Windows 和 macOS 平台上支持所有 Python 版本。我们甚至还在 Raspberry Pi 上运行了它!

此外,使用像 Dircmd[3] 这样的工具在你进入或退出 venv 文件夹时自动激活和停用虚拟环境也很棒!

谢谢,

SOURCEdefender

---

1: https://github.com/pyenv/pyenv-installer
2: https://pypi.ac.cn/project/sourcedefender/
3: https://github.com/dircmd/dircmd

Windows 上的 pyenv?请详细说明。Windows 上的 Python 很快就会变得混乱,因为当 Python 升级后,venv 会有不同的基础 Python。升级后我会从 requirements.txt 重新创建所有 venv。

回复 来自 SOURCEdefender (未验证)

为了简化常见的基于 venv 的工作流程,你可以使用 venvtools。

Venvtools 通过一系列命令使管理开发虚拟环境变得更加容易。

venvtools 命令类似于 git 命令。你只需按 [tab] 键两次即可进行命令操作(创建、列表、移除、激活、停用、跳转)和环境自动补全。

https://gitlab.com/Fahmi.Salleh/venvtools
https://gitlab.com/Fahmi.Salleh/venvtools/wikis/running

免责声明:我是 venvtools 的作者。如有任何错误或改进建议,请告知我。

确实很有趣!这篇文章有助于理解各种包管理器,特别是针对 Python 生态系统。

感谢分享

像这样选择要在环境中安装的特定版本的软件包/库

>> pip install =
例如,pip install fuzzysearch=0.6.0

为什么当我使用 pip 安装软件包时,它会强制已安装的软件包(以及关联的软件包)调用系统库,而使用 conda 安装相同的软件包时,它可以检查并在需要时修补本地安装?

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.