virtualenv 是管理 python 工程的利器,它可以很好的幫你維護項目中的依賴餐屎,使用 virtualenv析命,還能保持 global 庫的干凈、不會被不同項目中的第三方庫所污染局骤。
virtualenv 的默認功能簡單好用,可一旦涉及到多人協(xié)作暴凑,或部署到不同的環(huán)境中時峦甩,錯誤的使用 virtualenv 會給你帶來一些麻煩,從而你需要花很多時間在解決這些問題上现喳。本文的目的就是總結(jié)過去使用 virtualenv 的經(jīng)驗凯傲,希望能幫你找到一種正確的打開方式。
首先嗦篱,創(chuàng)建一個空的 virtualenv 時冰单,你的目錄中會包含以下文件和目錄
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
接著當(dāng)你執(zhí)行 source bin/activate
后,你安裝的依賴都會在 lib
目錄下灸促,這一點很誘人诫欠,會讓你覺得一切盡在掌握,因為該應(yīng)用程序所需要的一切庫文件全在這個 app 的根目錄下浴栽,所以當(dāng)這個應(yīng)用需要部署時荒叼,為了避免產(chǎn)生 ImportError: No module named xxx
錯誤,你會很容易的想到將本地這個 app 目錄打包典鸡,然后放到遠程服務(wù)器或容器中去執(zhí)行被廓。
當(dāng)你這么做時,你會發(fā)現(xiàn)雖然在遠程是可以執(zhí)行 source bin/activate
命令以進入 virtualenv 萝玷,但此時你引用的 python 可執(zhí)行文件卻并不是 ${app}/bin/pyhton
伊者,而是 global 環(huán)境中的那個 /usr/bin/python
英遭,所以 ${app}/lib
下的所有依賴包路徑仍然是沒有被包含進 sys.path
的。
這時亦渗,你才發(fā)現(xiàn)自己的假設(shè)是錯誤的,并開始懷疑自己使用 virtualenv 的方式存在問題汁尺,于是便 google 各種解決方案法精,但項目已處于部署階段,時間緊迫痴突,你很可能找不到最優(yōu)的辦法搂蜓,只能退而求其次,尋求次優(yōu)解辽装,畢竟依賴包都在嘛帮碰,改下 sys.path
不就好了嘛?確實很容易想到這種方法拾积,但又不想手動改殉挽,那就寫個程序改吧,也不難:
# 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
目錄下,這樣當(dāng)你啟動 global 的 python 時肛度,會自動將 pth.pth
里的路徑添加到 sys.path
下傻唾,這樣只需要在啟動你的 app 之前,執(zhí)行該腳本即可承耿,如下:
$ python set_sys_path.py
$ python main.py
問題暫時解決了冠骄,這次你的 app 也順利發(fā)布了;但還沒結(jié)束加袋,我們希望在測試機集群上把 app 的自動化測試做起來凛辣,在做自動化測試時,系統(tǒng)會隨機給你分配一臺機器資源锁荔,當(dāng)測試完成后蟀给,資源會被回收。你心想阳堕,這仍然很簡單嘛跋理,本地測試已經(jīng)覆蓋得很全了,只要自動化系統(tǒng)利用 git 把代碼拉下來恬总,先執(zhí)行 set_sys_path.py
設(shè)置 sys.path
前普,再執(zhí)行 python test.py
(測試入口)就可以了。
可這時又出現(xiàn)問題了壹堰,自動化測試在執(zhí)行 set_sys_path.py
時拭卿,報 Permission denied
錯誤骡湖,原因是測試機為了保持環(huán)境不被污染,不允許你將 pth.pth
復(fù)制到 global 的 site-packages
下峻厚。
遇到這個問題怎么辦响蕴?其實也很容易解決:我們都知道 python 中有個環(huán)境變量 PYTHONPATH
可以用來設(shè)置 sys.path
,既然沒有寫文件的權(quán)限惠桃,那定義環(huán)境變量總該可以吧:
$ export PYTHONPATH=$PYTHONPATH:${app}/lib/python2.7:${app}/lib/python2.7/site-packages
$ python main.py
果然可行浦夷,你再一次「順利」的完成了需求。
經(jīng)歷過多次折騰后辜王,我們發(fā)現(xiàn)這種使用 virtualenv 和修改 sys.path
的方法不算很好劈狐,還容易出錯。于是開始思考最初的那個問題呐馆,virtualenv 該怎么遷移肥缔?有沒有更好的辦法?答案肯定是有的汹来,在此之前续膳,我們先仔細觀察 virtualenv 產(chǎn)生的文件,會發(fā)現(xiàn)其中有 28 個軟連接俗慈,它們的源文件均在 global 庫中姑宽,如下所示
$ find . -type l
./.Python
./bin/python
./bin/python2
./include/python2.7
./lib/python2.7/lib-dynload
./lib/python2.7/encodings
...
所以,當(dāng)你把整個 virtualenv 打包闺阱,放到另一個環(huán)境中運行時炮车,肯定是會失敗的,因為軟連接失效了酣溃,于是,再一次證實這種把整個 virtualenv 打包的方法赊豌,實際上是錯誤的扛或,virtualenv 就只是一個 local 方案,而不是讓你可以「處處運行」的工具碘饼。
但 virtualenv 的隔離功能熙兔,可以讓你只關(guān)注項目范圍內(nèi)的依賴包,所以我們可以利用 pip freeze
命令艾恼,將項目內(nèi)的依賴保存到一個叫 requirements.txt
的文件中住涉,這樣在任何其他環(huán)境,我們只要根據(jù) requirements.txt
文件來安裝項目所需的依賴包钠绍,即可將本地的運行環(huán)境克隆出來舆声,而且這種克隆出來的環(huán)境更純粹,不會受到源環(huán)境或 global 庫的影響,沒有不確定性媳握。下面我們用一個例子來具體說明下:
假設(shè) Bob 和 Alice 同在一個團隊碱屁,他們決定使用 python 來開發(fā)新項目,一開始蛾找,Bob 在 github 上創(chuàng)建了一個新 repo娩脾,并在本地初始化它:
# 從 github clone 項目
$ git clone https://github.com/your_group/your_repo.git
$ cd your_repo
# 創(chuàng)建并進入 virtualenv
$ virtualenv .
$ source bin/activate
# 修改 .gitignore,過濾掉 virtualenv 產(chǎn)出的文件
$ 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
# 繼續(xù)開發(fā)
# ...
Bob 完成了初始化隘冲,實際上他只提交了 .gitignore
和 requirements.txt
兩個文件到 git 中,之后 Alice 也可以加入進來了:
# 從 github clone 項目
$ git clone https://github.com/your_group/your_repo.git
$ cd your_repo
# 創(chuàng)建并進入 virtualenv
$ virtualenv .
$ source bin/activate
# 根據(jù) requirements.txt 文件下載項目所需的依賴
$ pip install Flask gevent gunicorn -i https://pypi.mirrors.ustc.edu.cn/simple/
# 繼續(xù)開發(fā)
# ...
可以看到绑雄,通過這樣的步驟展辞,Bob 和 Alice 不僅有了一摸一樣的開發(fā)環(huán)境,還能最小化 git 倉庫的大小万牺,且按照這樣的思路罗珍,他們還可以把相同的環(huán)境克隆到測試機上,以及 Docker 鏡像中脚粟。顯然覆旱,這種一致性不僅可以提高開發(fā)效率,還可以提高后續(xù)的運維效率核无。
相關(guān)文章:
參考: