前言
俗话说,工作要做好自己,就一定要先利其器。 我们写代码也是如此。 在Python开发中,如何管理Python运行环境、package依赖关系是任何开发者都无法回避的问题。 在PyCon2018上,热情之石Reitz介绍的Pipenv是解决这类问题的大杀器。
为什么需要Pipenv?
要理解hxdym Reitz为什么开发Pipenv,必须从Python的软件包管理工具的发展历史开始。
python打包的历史
Distutils
早期的Python提供了一个名为distutils的内置模块。 使用此模块,开发人员可以创建自己的package的setup.py文件,并将其全部打包上载到internet。 如果用户想要安装此包,则首先从网上下载文件(通常是tar包等),解压缩后运行Python setup.py install,然后运行python的site-packages
PyPI
PyPI的全名是python package索引,可以理解为集中索引,开发人员可以在此上传package及其元数据。 如果有PyPI,其他开发人员可以从上面下载所需的软件包,然后运行python setup.py install进行安装。 尽管如此,还是有一些问题:
整个过程需要人手,不便于自动化的package都是全球安装的,不能同时安装同一package的两个不同版本,很麻烦,用户体验很差的Setuptools
Setuptools的出现弥补了distutils中存在的一些缺点,提供了更丰富的功能。 可以认为Setuptools是对distutils的一系列扩展,如egg安装文件的支持、自动安装工具(easy_install )、distutils的monkey-patch等。 如果有easy_install,当用户想要安装某个package时,只需要执行easy_install package,工具就会自动将package及其依赖关系(默认情况下从官方的PyPI开始)导出到文件夹中与以前的软件包安装方法相比,easy_install具有以下优点:
更好的用户安装体验大多数package来自PyPI,适合自动化。 关于缺点,最重要的是没有easy_uninstall。 也就是说,只能使用easy_install安装软件包,但没有适当的工具来卸载。
pip
到2008年,pip作为easy_install的替代者出现。 虽然pip的大部分构建在setuptools的各个部件之上,但是与easy_install相比提供了更强大的功能。 特别是引入了Requirements Files的概念,使用户可以轻松复制Python环境。 如果在一个环境中运行pip freeze requirements.txt,导出当前环境中的所有包信息,然后在新环境中运行pip安装- r请求. txt,则pip将返回这些包如果不需要软件包,也可以运行pip卸载软件包进行卸载。 迄今为止,pip已经成为最受Python开发者欢迎的软件包管理工具。
虚拟现实
pip解决了单个环境中的大部分package管理问题,但通常在一台机器上同时开发多个项目。 项目a需要Python2.7和Flask0.9,项目b需要Python3.6和Flask1.0,项目c需要Python3.6和Flask1.0.2。 这样,我们面临着两个问题。
对于项目a和b,或者项目a和c,如何区分它们使用的不同版本的Python和快速开关? 对于项目b和c,由于使用的是Python3.6,因此安装的第三方软件包位于Python3.6的site-packages目录下。 那么,如何区分所需的不同版本的Flask呢? 对于第一个问题,可以安装所有必要的Python,分别指定不同的alias,在开发不同的项目时使用不同的alias。 虽然此方法有效,但很复杂,容易出错,如果开发人员忘记使用alias或使用了错误的alias,则可能会在错误版本的Python下安装软件包。
关于第二个问题,光靠pip很难解决。 因为同一版本的Python的所有第三方包都在site-packages下,无法区分不同的版本。
要解决上述问题,需要一个名为virtualenv的新工具。 virtualenv可以为每个项目创建隔离的Python环境,以防止系统中不同的Python环境之间相互影响。 在每个隔离的环境下,使用pip进行软件包管理。 pip virtualenv是目前主流的Python开发过程。
更进一步
如上所述,pip virtualenv的工作方式成为主流,至今仍在继续。 但是,这种方法也有一些不足。
新人(特别是不知道Unix相关概念的新人)很难分辨virtualenv的抽象层是什么样的vi
rtualenv的工作流程比较繁琐,对人来说不够自然,尽管virtualenv-wrapper的出现一定程度上缓解了这个问题pip的requirements.txt过于简单,没法表示具体的依赖关系需要使用两个工具(pip+virtualenv)才能完成工作,不够便捷下面是在只安装了Flask的环境中执行pip freeze导出的requirements.txt。可以看到,里面包含了Flask本身及其依赖,每个package的版本都是确定的,但是没法看出它们之间的具体依赖关系是怎样的。试想,如果我们想使用一个开源项目,看到这样一个requirements.txt,我们可能会误以为这个项目直接依赖了这些packages,但实际上它只是直接依赖了Flask。
另一种requirements.txt的写法就是,我们只给定需要直接依赖的package名称,像下面这样。使用这种方式,我们一眼就能看出项目直接依赖了哪些package。但是这里有个问题,即Flask及其依赖的版本是不确定的。如果过段时间某个依赖发布了新版本,你去新环境部署的时候pip就会给你装上新的版本,可能会导致你的代码没法工作。
以上就是激情的石头的演讲中举的例子,用来说明"what you want"和"what you need"之间的不匹配。
Pipfile & Pipfile.lock
为了解决"what you want"和"what you need"之间的不匹配问题,Pipfile这个新的标准被提了出来。
Pipfile被设计用来取代requirements.txt。其优点主要在于:
采用TOML语法,相比requirements.txt表达能力更强默认支持两组依赖:[packages]和[dev-packages],可以将多个requirements.txt的内容合并到一个文件,方便管理可以通过Pipfile.lock对环境进行明确、详细地描述Pipfile大致是这么个样子:
通过对Pipfile进行处理,可以生成JSON格式的Pipfile.lock,包含了所有依赖及其具体的版本号,还有每个release的hash。比如下面:
大家可以理解成,Pipfile只描述了你想要的package是哪些,是抽象而宽泛的,比如上面Pipfile的例子描述了我们需要Flask这个package。而Pipfile.lock则是对你在实际运行环境里需要的package以及它们所有依赖的描述,是具体而明确的,比如上面Pipfile.lock的例子描述了Flask以及其依赖的具体信息,这样当我们想在新环境里运行我们的项目时,就可以按照这些信息来安装所有依赖的package,确保环境的一致性。实际上,很多语言的package管理工具都支持类似Pipfile.lock这样的Lockfile,比如Node.js的yarn和npm,PHP的Composer,Rust的Cargo以及Ruby的Bundler。
Pipenv
激情的石头 Reitz开发的Pipenv,将Pipfile,pip和virtualenv整合到了一起,让我们只使用这一个工具就可以非常方便、流畅地管理自己的Python环境。Pipenv的主要优点:
可以让你无缝使用Pipfile和Pipfile.lock,保证每个依赖的信息都是明确的提供简洁的命令帮你操作virtualenv提供其他辅助工具,比如pipenv graph,可以显示项目完整的依赖关系现在Pipenv已经是Python官方推荐的工作流(package管理+virtual env管理)工具了。
Pipenv用法简介
首先安装pipenv:
然后我们创建一个workspace并切换到该目录下(我这里是~/workspaces/pipenv_demo),创建一个新的环境:
如果要指定Python版本,可以使用--python参数:
创建完后,目录下就会生成Pipfile和Pipfile.lock两个文件:
下一步,我们安装Requests:
安装完毕之后,我们Pipfile就会变成下面这个样子:
而Pipfile.lock则是这样:
运行pipenv graph可以将环境中的完整依赖打印出来:
这个时候,如果我们直接运行Python交互模式,尝试import requests会报错,因为还没有激活virtual env:
Pipenv提供了一个非常好用的命令:pipenv shell,用于激活virtual env:
可以看到,当激活virtual env后,命令行提示符前面多了'(pipenv_demo-B6h7SXri)',这个就相当于我们virtual env的id,表示我们现在处于这个virtual env下。再次尝试在交互模式中import requests,成功:
当不需要virtual env时,只需要运行exit即可:
通常我们需要把Pipfile和Pipfile.lock也加到版本管理中,以能保证同一个项目的不同开发者的Python环境保持一致。比如我们新加入了一个项目,就可以把repo clone下来,直接运行pipenv install,pipenv会自动找到已存在的Pipfile和Pipfile.lock,并根据里面的信息来安装依赖,这样我们就能准确无误地复制其他人的环境了。
总结
就像激情的石头 Reitz演讲标题所写的那样,Pipenv是Python依赖管理的未来。作为一名合格的Python开发者,还是有必要学习下这个工具,提升自己的工作效率,也享受更好的工作体验。
参考
Pipenv - The Future of Python Dependency Management