Python 工程管理及 virtualenv 的迁移

virtualenv 是管理 python 工程的利器,它能够很好的帮你维护项目中的依赖,使用 virtualenv,还能保持 global 库的干净、不会被不一样项目中的第三方库所污染。html

virtualenv 的默认功能简单好用,可一旦涉及到多人协做,或部署到不一样的环境中时,错误的使用 virtualenv 会给你带来一些麻烦,从而你须要花不少时间在解决这些问题上。本文的目的就是总结过去使用 virtualenv 的经验,但愿能帮你找到一种正确的打开方式。python

首先,建立一个空的 virtualenv 时,你的目录中会包含如下文件和目录git

drwxr-xr-x   7 fengyajie  staff   224B Mar 21 22:49 .
drwxr-xr-x   8 fengyajie  staff   256B Mar 21 20:28 ..
lrwxr-xr-x   1 fengyajie  staff    83B Mar 21 22:49 .Python -> /usr/local/Cellar/...
drwxr-xr-x  16 fengyajie  staff   512B Mar 21 22:49 bin
drwxr-xr-x   3 fengyajie  staff    96B Mar 21 22:49 include
drwxr-xr-x   3 fengyajie  staff    96B Mar 21 22:49 lib
-rw-r--r--   1 fengyajie  staff    61B Mar 21 22:49 pip-selfcheck.json
复制代码

接着当你执行 source bin/activate 后,你安装的依赖都会在 lib 目录下,这一点很诱人,会让你以为一切尽在掌握,由于该应用程序所须要的一切库文件全在这个 app 的根目录下,因此当这个应用须要部署时,为了不产生 ImportError: No module named xxx 错误,你会很容易的想到将本地这个 app 目录打包,而后放到远程服务器或容器中去执行。github

当你这么作时,你会发现虽然在远程是能够执行 source bin/activate 命令以进入 virtualenv ,但此时你引用的 python 可执行文件却并非 ${app}/bin/pyhton,而是 global 环境中的那个 /usr/bin/python,因此 ${app}/lib 下的全部依赖包路径仍然是没有被包含进 sys.path 的。shell

这时,你才发现本身的假设是错误的,并开始怀疑本身使用 virtualenv 的方式存在问题,因而便 google 各类解决方案,但项目已处于部署阶段,时间紧迫,你极可能找不到最优的办法,只能退而求其次,寻求次优解,毕竟依赖包都在嘛,改下 sys.path 不就行了嘛?确实很容易想到这种方法,但又不想手动改,那就写个程序改吧,也不难:json

# set_sys_path.py
def set_sys_path():
    import sys
    for path in sys.path:
        if os.path.split(path)[1] == 'site-packages':
            home = os.path.abspath(os.path.dirname(__file__))
            pypath = os.path.join(home, 'lib/python2.7')
            pypath_sitepackage = os.path.join(home, 'lib/python2.7/site-packages')
            pth = os.path.join(path, 'pth.pth')
            with open(pth, 'w') as f:
                    f.write("%s\n" % pypath)
                    f.write("%s\n" % pypath_sitepackage)

if __name__ == "__main__":
    set_sys_path()
复制代码

上面的程序很简单,它将 ${app}/lib/python2.7${app}/python2.7/site-packages 两个依赖路径写到 pth.pth 文件中,并将该文件 mv 到 global 的 site-packages 目录下,这样当你启动 global 的 python 时,会自动将 pth.pth 里的路径添加到 sys.path 下,这样只须要在启动你的 app 以前,执行该脚本便可,以下:bash

$ python set_sys_path.py
$ python main.py
复制代码

问题暂时解决了,此次你的 app 也顺利发布了;但还没结束,咱们但愿在测试机集群上把 app 的自动化测试作起来,在作自动化测试时,系统会随机给你分配一台机器资源,当测试完成后,资源会被回收。你心想,这仍然很简单嘛,本地测试已经覆盖得很全了,只要自动化系统利用 git 把代码拉下来,先执行 set_sys_path.py 设置 sys.path,再执行 python test.py(测试入口)就能够了。服务器

可这时又出现问题了,自动化测试在执行 set_sys_path.py 时,报 Permission denied 错误,缘由是测试机为了保持环境不被污染,不容许你将 pth.pth 复制到 global 的 site-packages 下。app

遇到这个问题怎么办?其实也很容易解决:咱们都知道 python 中有个环境变量 PYTHONPATH 能够用来设置 sys.path,既然没有写文件的权限,那定义环境变量总该能够吧:运维

$ export PYTHONPATH=$PYTHONPATH:${app}/lib/python2.7:${app}/lib/python2.7/site-packages
$ python main.py
复制代码

果真可行,你再一次「顺利」的完成了需求。

经历过屡次折腾后,咱们发现这种使用 virtualenv 和修改 sys.path 的方法不算很好,还容易出错。因而开始思考最初的那个问题,virtualenv 该怎么迁移?有没有更好的办法?答案确定是有的,在此以前,咱们先仔细观察 virtualenv 产生的文件,会发现其中有 28 个软链接,它们的源文件均在 global 库中,以下所示

$ find . -type l
./.Python
./bin/python
./bin/python2
./include/python2.7
./lib/python2.7/lib-dynload
./lib/python2.7/encodings
...
复制代码

因此,当你把整个 virtualenv 打包,放到另外一个环境中运行时,确定是会失败的,由于软链接失效了,因而,再一次证明这种把整个 virtualenv 打包的方法,其实是错误的,virtualenv 就只是一个 local 方案,而不是让你能够「到处运行」的工具。

但 virtualenv 的隔离功能,可让你只关注项目范围内的依赖包,因此咱们能够利用 pip freeze 命令,将项目内的依赖保存到一个叫 requirements.txt 的文件中,这样在任何其余环境,咱们只要根据 requirements.txt 文件来安装项目所需的依赖包,便可将本地的运行环境克隆出来,并且这种克隆出来的环境更纯粹,不会受到源环境或 global 库的影响,没有不肯定性。下面咱们用一个例子来具体说明下:

假设 Bob 和 Alice 同在一个团队,他们决定使用 python 来开发新项目,一开始,Bob 在 github 上建立了一个新 repo,并在本地初始化它:

# 从 github clone 项目
$ git clone https://github.com/your_group/your_repo.git
$ cd your_repo
# 建立并进入 virtualenv
$ virtualenv .
$ source bin/activate
# 修改 .gitignore,过滤掉 virtualenv 产出的文件
$ cat .gitignore
*.py[cod]
__pycache__/
.Python
bin/
include/
lib/
pip-selfcheck.json
# 在本地安装基本依赖,例如 Flask、gevent、gunicorn 等
$ pip install Flask gevent gunicorn -i https://pypi.mirrors.ustc.edu.cn/simple/
# 将本地依赖写入 requirements.txt
$ pip freeze > requirements.txt
# 将变动提交到 github
$ git add .
$ git commit -m "init project"
$ git push origin master
# 继续开发
# ...
复制代码

Bob 完成了初始化,实际上他只提交了 .gitignorerequirements.txt 两个文件到 git 中,以后 Alice 也能够加入进来了:

# 从 github clone 项目
$ git clone https://github.com/your_group/your_repo.git
$ cd your_repo
# 建立并进入 virtualenv
$ virtualenv .
$ source bin/activate
# 根据 requirements.txt 文件下载项目所需的依赖
$ pip install Flask gevent gunicorn -i https://pypi.mirrors.ustc.edu.cn/simple/
# 继续开发
# ...
复制代码

能够看到,经过这样的步骤,Bob 和 Alice 不只有了一摸同样的开发环境,还能最小化 git 仓库的大小,且按照这样的思路,他们还能够把相同的环境克隆到测试机上,以及 Docker 镜像中。显然,这种一致性不只能够提升开发效率,还能够提升后续的运维效率。

相关文章:

参考:

相关文章
相关标签/搜索