预计所需阅读时间:11分钟

转载自CSDN博客,作者:Forskamse

原文链接:https://forskamse.blog.csdn.net/article/details/98665869

前言

工程实际应用时,我们需要考虑如何在各种情况下顺利地将工程的运行环境部署起来。
就Python工程来说,最主要的就是将程序运行所需的各种依赖模块安装起来。目前Python最常用包管理工具是conda和pip,其中conda还具有虚拟环境管理功能,而且conda环境下可能还有很多包是通过pip安装的。
避免混乱,我将分两篇文章分别介绍纯粹使用pip以及使用conda虚拟环境时的部署方法。这篇文章,从简单的pip开始。

教条的方法

pip提供了导出依赖的方法,在测试机器上执行以下命令导出依赖文件requirements.txt:

pip freeze >  requirements.txt

文件格式如下:

<package>==<version>

随后,在待部署机器上,使用以下命令安装就可以恢复依赖环境:

pip install -r requirements.txt

网上能搜到的大部分教程,大都是这么写的。所以我称之为教条的方法。如果真的有这么畅通无阻,那就真的太好了,毕竟谁都不愿意折腾。但是事实上,在实际部署中,存在这样那样的各种问题,想不折腾的话,请大家看下去。

找不到匹配的包

一个最常遇到的问题是:找不到匹配的包(No matching distribution found)。在这个提示信息之前,还会有Could not find a version that satisfies the requirement … 。很多情况下确实只是找不到适配的版本,而有的时候是真的找不到包。

pip的镜像源处于不断更新的状态,而且更新异常活跃,甚至有人发布了包又将其撤回、或者删去之前的版本,都可能造成找不到对应版本的包的情况。

例如:

而如果一些包来源于外部链接,在pip仓库中并没有的话,得到的提示仍然是No matching distribution found,这时pip是真找不到这个包了。例如:

属于第一种情况的,有以下几个可能的解决方法:

(1)调整requirements.txt,把“==”变成“>=”。这样即使源更新了,还是能找到绝大多数包的。但是,这方法其实只是取巧。调整后的整体依赖环境是否能够正常、稳定地工作,并没有得到验证。

(2)更新测试机器上的包,在测试机器上验证是否能够正常、稳定地工作后,再导出requirements.txt。

批量更新pip的包可以使用:

pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1  | xargs -n1 pip install -U
(如果不支持的话可能是pip版本太旧,先升级下pip)

这个方法也存在一些问题。
首先,升级新版本是冒险的,由其对于开发周期较长的程序来说,开发过程中,可能所依赖的包的版本已经有了较大的更新,程序中用到的一些方法可能在新版本中都被取消了,所以如果要升级,升级后一定要确保程序可以正常且稳定地运行。这个可能就比较耗时了。
其次,如果测试机器上升级完并导出后,并不具备快速进行部署的条件,升级也是枉然的,间隔几天后进行部署,依然面临一些包不匹配的问题。这个问题的根源就是pip源的不断更新,那么,要彻底解决这个问题,就得建立自己的源或者在安装时指定本地文件了。这一点,后面在离线部署时一并介绍。

属于第二种情况的,可以用以下方法解决:

(1)在requirements.txt中,相应的包之前添加–find-links,例如:

--find-links https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.1.0/en_core_web_sm-2.1.0.tar.gz
en_core_web_sm==2.1.0

离线部署

源的不断更新可以令用户更快用上来自上游的更新,但是对部署来说却未必是一件好事。
为了实现离线部署,可以将安装所需的文件从源上下载下来,然后从本地安装。
首先,还是需要导出requirements.txt文件,然后在测试机器上使用以下命令下载所有的安装包:

pip download -d ~/my_env_pkgs/ -r requirements.txt

在下载文件的过程中,我们依然会遇到找不到适配的包的问题。这时要做的是,仔细对照这些包,如果并不是十分重要的包,可以在本地更新修改requirements.txt文件,下载更新的版本;如果非常关键的包(比如做的深度学习的项目,用到的深度学习框架),应该去网上搜索,找到旧版本的包,放到~/my_env_pkgs/ 目录下,requirements.txt中可以暂时删除这一项;通过外部链接安装的包,就将包先下载到~/my_env_pkgs/ 目录下,同样地,requirements.txt中可以暂时删除这一项。

如此一来,requirements.txt文件(记得补上刚刚暂时删去的项)中的依赖与所提供的文件及版本就是完全一致的了,在部署时就不会遇到上述的问题。

复制到待部署机器上后,可以使用以下命令安装:

pip install -r requirements.txt --no-index --find-links=file:///home/user_name/my_env_pkgs

其中 --no-index代表不去搜索源,file://是必须的,此后再接上绝对路径(一定要使用绝对路径,所以有三个/)。

当然,你也可以构建一个本地的pypi源:

# 安装pip2pi模块
pip install pip2pi
# 建立索引(my_env_pkgs目录下会多出一个simple文件夹)
dir2pi ~/my_env_pkgs/
# 进入目标文件夹(就是对外发布的文件夹,因为开启HTTP Server是将当前文件夹发布)
cd ~/my_env_pkgs/simple/
# 开启HTTP Server - Python3
python -m http.server
# 开启HTTP Server - Python2
python -m SimpleHTTPServer

然后使用以下命令安装:

pip install -i http://127.0.0.1:8000/ -r requirements.txt

二者无甚区别。

跨平台部署

当需要跨平台部署环境时,最妥当的方法应该是在不同的平台上做好测试,然后再按以上方法分别部署。我并不建议大家用我下面说的方法直接部署,而是希望大家将其当做减少在新测试机器上部署测试环境的工作量的方法。
一般来说,在发布一个模块时,开发者会同时发布支持多个平台的独立的binary distribution(扩展名为whl)和一个source distribution(简称sdist,扩展名为tar.gz),但这并不是强制要求,所以存在例外。
在前面的方法中,我们稍作修改:

pip download --no-binary=:all: -d ~/my_env_pkgs/ -r requirements.txt

这是要求pip只下载sdist,而不下载binary distribution。
在新测试机器上,仍然使用以下命令安装,不需更改:

pip install -r requirements.txt --no-index --find-links=file:///home/user_name/my_env_pkgs

此时,在新测试机器上,pip的setuptools将会根据平台环境和源码去构建合适的whl包,继而安装。
对于那些不提供sdist的包,则需要在新测试机器上独立安装了。

其他的小问题

(1)依赖安装顺序问题

pip导出的requirements.txt文件中各项是按字母表顺序排序的,因此其中存在一些属于前面需要安装的包的依赖包的包被放到了后面。正常情况下,安装这些包时,也会进行依赖的检查,并不会出什么问题,但是,由于pip的处理逻辑是whl下载下来后不会先安装,但是sdist下载下来后会先编译成whl,这时,就可能用到这些依赖,这应该是个小bug吧。如果遇到这个问题,就先安装下这个依赖吧。

(2)python版本与pip版本
最起码应保证测试机器与待部署机器上的python版本与pip版本的一致,避免语法上的错误或者其他不可预见的错误。

关于其他工具的说明

(1)pip-bundle

pip-bundle可以在导出的requirements.txt的基础上一键创建一个bundle文件,实际上就是把所有的需要的安装的包融合到一起。具体如下:

# 安装(测试机器和待部署机器上都需要)
pip install pip-bundle
# 测试机器上下载包
pip-bundle create my_env.pip-bundle
# 待部署机器上安装这些包
pip-bundle install my_env.pip-bundle

需要注意的是,pip-bundle这个工具也依赖于requirements.txt,所以还是存在“找不到匹配的包”的问题,所以,该更新还是得更新,该修改requirements.txt文件,还是得修改,参照上面的方法。对于那类从外部源安装的包,则需要用–find-links在requirements.txt中做好标注了。

遗憾的是,这个工具还在打包时编译成whl,也就使其失去了跨平台使用的特性。

(2)pipreqs

pipreqs可以通过对项目目录下文件的扫描,确定文件运行需要哪些依赖,在pip环境不纯净时可以用于替代pip freeze。
具体用法如下:

# 安装
pip install pipreqs
# 进入项目目录
cd ~/my_project/
# 导出requirements.txt
pipreqs ./ --encoding=utf8

(3)pyinstaller

如有打包成exe文件的需求,可以看看pyinstaller这个模块。

(4)查找资料时发现一个将文件打包成zip的方法,具体是否还奏效我没有去测试,感兴趣的自己看看吧,链接如下:

https://www.zhihu.com/question/21639330

https://blogs.gnome.org/jamesh/2012/05/21/python-zip-files/

参考文献

https://pip.readthedocs.io/en/1.1/requirements.html

https://pip.pypa.io/en/stable/reference/pip_wheel/

https://blog.csdn.net/iodjSVf8U1J7KYc/article/details/90585771

https://stackoverflow.com/questions/18883430/trouble-installing-private-github-repository-using-pip

https://pypi.org/project/pip-bundle/

https://www.zhihu.com/question/21639330

https://blogs.gnome.org/jamesh/2012/05/21/python-zip-files/