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 完成了初始化,實際上他只提交了 .gitignore
和 requirements.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 鏡像中。顯然,這種一致性不只能夠提升開發效率,還能夠提升後續的運維效率。
相關文章:
參考: