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 发行版都使用内置的包管理器(dnf、apt、pacman、brew 等)来管理 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,还是 apt、dnf、pacman 等?
答案是:视情况而定。
pip 通常用于直接从 PyPI 安装包,Python 包作者通常在那里上传他们的包。但是,大多数包维护者不会使用 PyPI,而是从作者创建的源代码分发 (sdist) 或版本控制系统(例如 GitHub)中获取源代码,在需要时应用补丁,并为各自的平台测试和发布包。与 PyPI 分发模型相比,这有优点和缺点
- 由原生包管理器维护的软件通常更稳定,并且通常在给定平台上运行得更好(尽管情况可能并非总是如此)。
- 这也意味着打包和测试上游 Python 代码需要额外的工作
- 包选择通常比 PyPI 提供的少得多。
- 更新较慢,并且包管理器通常会发布较旧的版本。
如果我们想要使用的包可用,并且我们不介意稍微旧的版本,则包管理器提供了一种方便且安全的方式来安装 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 二进制文件和包管理的基本工具:setuptools、pip 和 wheel。
创建虚拟环境
virtualenv 是一个第三方包,但 Python 3.3 将 venv 包添加到标准库中。因此,我们不必安装任何东西即可在现代 Python 版本中使用虚拟环境。我们可以简单地使用 python3.7 -m venv <env_name> 来创建一个新的虚拟环境。
创建新的虚拟环境后,我们必须通过在新建环境的 bin 目录中运行 activate 脚本来激活它。激活脚本创建一个新的子 shell,并将 bin 目录添加到 PATH 环境变量,使我们能够从此位置运行二进制文件和脚本。这意味着此子 shell 将使用在此位置安装的 python、pip 或任何其他工具,而不是全局安装在系统上的那些工具。
$ 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,而无需手动更改环境变量,如 PATH 或 PYTHONPATH。
(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。
- 如果您想使包对机器的所有用户可用,您拥有正确的权限,并且该包可用,则使用您的发行版的包管理器(apt、yum、pacman、brew 等)。
- 如果您没有 root 权限或操作系统包管理器没有您需要的包,请使用 pip install --user 并将用户安装目录添加到 PATH 环境变量。
- 如果您希望同一库的多个版本共存,进行 Python 开发,或者只是出于任何其他原因隔离依赖项,请使用虚拟环境。
本文最初于 2019 年 4 月发布,并由编辑更新。
11 条评论