6.3 虚拟环境:pipenv¶
1.pipenv解决的问题¶
1.1 requirements.txt依赖管理的局限
如果我要使用 flask, 我会在requirements.txt里面写上
flask
不过由于没有指定版本,因此在另一个环境通过pip install -r requirements.txt安装依赖模块时,会默认安装最新版本的flask,如果新版本向后兼容,这当然是没问题的。但是如果新版本不兼容旧的接口,那么就出问题了:代码无法在该环境运行。因此测试环境和生产环境的不一致出现了,同一份requirement.txt,结果出来2份不同的环境,这叫做不确定构建 (the build isn’t deterministic) 问题。
这时候,可以考虑加上版本号,requirements.txt这么写
flask==0.12.1
这么写肯定可以了吧?因为以后在新环境安装pip install -r requirements.txt的时候,用的是该指定版本,就不会发生跑不起来的情况。
是这样吗?不一定哟,我们再分析一下:因为flask本身还依赖于其他模块,因此执行pip install -r requirements.txt的时候,flask使用的是0.12.1版本,但是它的依赖模块可不一定相同。比如说flask依赖Werkzeug模块,而Werkzeug模块的新版本有bug!
这时候,传统的解决方式是使用pip freeze, 它能给出当前环境下第三方模块和所有依赖子模块的版本号,因此在生产环境就可以确保使用与开发环境相同的模块了。这样我就可以把pip freeze的输出写入到requirement.txt:
click==6.7
Flask==0.12.1
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
Werkzeug==0.14.1
这或许解决了生产与开发环境不一致的问题,可是它又引入了一大坨新问题!
因为,也许Werkzeug==0.14.1隐藏了一个漏洞,官方发布了0.14.2版本修复了该问题,那么我不仅需要及时下载更新模块,还得去更新requirememts.txt文件。但是那么多模块,我难道要把所有更新版本都记录下来?事实上,我本不需要记录那么多我不关心的模块的版本,我只想记录自己关心的核心模块版本。
这就出现了一个矛盾:与之间的矛盾。
1.3 多个项目依赖不同版本的子模块
假如我有2个项目A和B,A依赖django==1.9,B依赖django==1.10。linux默认使用全局共享的依赖包,因此当我在A和B项目间切换时,需要检测–卸载–安装django。
传统的解决方式是使用virtual environment ,将项目A和B的第三包隔离开。python2目前有virtualenv方案,python3目前有venv方案。
而pipenv也集成了虚拟环境管理的功能。
1.3 依赖分析
先解释一下什么叫依赖分析,假如我有一个requirments.txt如下:
package_a
package_b
如果package_a依赖于package_c>=1.0,package_b依赖于同样的package_c<=2.0, 那么在安装a,b模块时,就只能在(1.0,2.0)的中选择package_c的版本,如果安装工具有这种能力,就说它能做依赖分析。
不过pip工具没有这种依赖分析的功能。这时候只能通过在requirement.txt里面添加package_c的版本范围才能解决问题,好傻:
package_c>=1.0,<=2.0
package_a
package_b
2. 安装pipenv¶
$ pip install pipenv
在指定目录下创建虚拟环境, 会使用本地默认版本的python
$ pipenv install
如果要指定版本创建环境,可以使用如下命令,当然前提是本地启动目录能找到该版本的python
$ pipenv --python 3.6
激活虚拟环境
$ pipenv shell
安装第三方模块, 运行后会生成Pipfile和Pipfile.lock文件
$ pipenv install flask==0.12.1
当然也可以不指定版本:
$ pipenv install numpy
如果想只安装在开发环境才使用的包,这么做:
$ pipenv install pytest --dev
无论是生产环境还是开发环境的包都会写入一个Pipfile里面,而如果是用传统方法,需要2个文件:dev-requirements.txt 和 test-requirements.txt。
接下来如果在开发环境已经完成开发,如何构建生产环境的东东呢?这时候就要使用Pipfile.lock了,运行以下命令,把当前环境的模块lock住, 它会更新Pipfile.lock文件,该文件是用于生产环境的,你永远不应该编辑它。
$ pipenv lock
然后只需要把代码和Pipfile.lock放到生产环境,运行下面的代码,就可以创建和开发环境一样的环境咯,Pipfile.lock里记录了所有包和子依赖包的确切版本,因此是确定构建:
$ pipenv install --ignore-pipfile
如果要在另一个开发环境做开发,则将代码和Pipfile复制过去,运行以下命令:
$ pipenv install --dev
由于Pipfile里面没有所有子依赖包或者确定的版本,因此该安装可能会更新未指定模块的版本号,这不仅不是问题,还解决了一些其他问题,我在这里做一下解释:
假如该命令更新了一些依赖包的版本,由于我肯定还会在新环境做单元测试或者功能测试,因此我可以确保这些包的版本更新是不会影响软件功能的;然后我会pipenv lock并把它发布到生产环境,因此我可以确定生产环境也是不会有问题的。这样一来,我既可以保证生产环境和开发环境的一致性,又可以不用管理众多依赖包的版本,完美的解决方案!
3. 操作虚拟环境¶
# 返回项目的路径
$ pipenv --where
# 返回虚拟环境路径
$ pipenv --venv
# 返回该虚拟环境的解释器
$ pipenv --py
# 进入这个虚拟环境
$ pipenv shell
# 退出这个虚拟环境
$ exit
$ deactivate
# 移除当前目录的虚拟环境
$ pipenv --rm
# 在当前虚拟环境中运行
$ pipenv run python # 进入交互式,跟直接执行 python 一样
$ pipenv run python 文件名 # 运行文件
$ pipenv run pip ... # 运行pip
4. 虚拟环境包管理¶
# 安装一个本地包(setup.py)到虚拟环境(Pipfile)
$ pipenv install -e .
# 安装、卸载模块
$ pipenv install requests
$ pipenv uninstall requests
$ pipenv uninstall --all # 卸载全部包
$ pipenv install -r path/to/requirements.txt
# 安装所有依赖
$ pipenv install --dev
# 更新包
$ pipenv update # 更新所有包
$ pipenv update --outdated # 打印所有要更新的包
$ pipenv update <包名> # 更新指定的包
# 将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件
$ pipenv run pip freeze # 相当于pipenv run pip freeze >requirements.txt
$ pipenv lock -r > requirements.txt
$ pipenv lock -r --dev # 若只想导出开发用的包
# 创建一个包含预发布的锁文件:
$ pipenv lock --pre
# 打印所有包的依赖关系图
$ pipenv graph
# 检查安全漏洞
$ pipenv check
###5. 旧项目的requirments.txt转化为Pipfile
使用pipenv install会自动检测当前目录下的requirments.txt, 并生成Pipfile, 我也可以再对生成的Pipfile做修改。
此外以下命令也有同样效果, 可以指定具体文件名:
$ pipenv install -r requirements.txt
如果我有一个开发环境的requirent-dev.txt, 可以用以下命令加入到Pipfile:
$ pipenv install -r dev-requirements.txt --dev