正确管理 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 年,有几个名称类似于流行的 Python 库的包被上传到 PyPI。上传的代码收集了系统和用户信息并将其上传到远程服务器。这些包很快就被撤下了。但是,由于任何人都可以将包上传到 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 目录中运行 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 是 Bloomberg 的高级软件工程师,他在 Python 基础设施团队工作。他拥有近 15 年的行业经验,并参与过各种各样的项目,例如为向量处理器优化代码、创建分布式 JVM 以及自动生成新闻内容。

11 条评论

感谢这篇文章,它早就应该出现了。我在 Ubuntu 中使用系统库与我的库斗争了很长时间。这是一个绝对的噩梦,似乎没有人解释。

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

回复 ,作者 lkollar

非常感谢你没有支持 pipenv 这个垃圾堆

python3.7 现在支持这个真是太棒了。我个人仍然更喜欢 miniconda,仅仅是因为我可以安装一些包而无需 make、gcc,...
此外,conda 也带有 pip,因此您可以安装 conda 仓库中没有的包,从而实现两全其美。

windows 上的 pyenv?请详细说明。windows 上的 Python 很快就会变成一团糟,因为当 python 升级时,venvs 有不同的基础 python。我在升级后从 requirements.txt 重新创建所有 venv。

回复 ,作者 SOURCEdefender (未验证)

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

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

venvtools 命令类似于 git 命令。您只需按 [tab] 键两次即可进行命令操作(create、list、remove、activate、deactivate、goto)和环境自动完成。

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 安装同一个包时,它也可以检查并在需要时修补本地安装?

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.