作為一個希望把所有的事情都準備好的死理性派瓣赂,我對python的開發(fā)環(huán)境非常重視李命,不過python常常面臨生產(chǎn)環(huán)境和開發(fā)環(huán)境不一致侄非;或者在A處開發(fā)一陣子蕉汪,又放到B處開發(fā)的問題,之前的pip以及requirement有諸多問題逞怨,pipenv解決了這些問題者疤,并給出了最佳實踐方案。我專門編譯了一篇文章叠赦,希望能夠幫助到自己和大家:https://realpython.com/pipenv-guide/#package-distribution
pipenv所解決的問題
requirements.txt依賴管理的局限
如果我要使用 flask, 我會在requirements.txt里面寫上
flask
不過由于沒有指定版本驹马,因此在另一個環(huán)境通過pip install -r requirements.txt
安裝依賴模塊時,會默認安裝最新版本的flask除秀,如果新版本向后兼容糯累,這當然是沒問題的。但是如果新版本不兼容舊的接口册踩,那么就出問題了:代碼無法在該環(huán)境運行泳姐。因此測試環(huán)境和生產(chǎn)環(huán)境的不一致出現(xiàn)了,同一份requirement.txt暂吉,結(jié)果出來2份不同的環(huán)境胖秒,這叫做 不確定構(gòu)建 (the build isn’t deterministic) 問題。
這時候借笙,可以考慮加上版本號扒怖,requirements.txt這么寫
flask==0.12.1
這么寫肯定可以了吧较锡?因為以后在新環(huán)境安裝pip install -r requirements.txt
的時候业稼,用的是該指定版本,就不會發(fā)生跑不起來的情況蚂蕴。
是這樣嗎低散?不一定喲,我們再分析一下:因為flask本身還依賴于其他模塊骡楼,因此執(zhí)行pip install -r requirements.txt
的時候熔号,flask使用的是0.12.1版本,但是它的依賴模塊可不一定相同鸟整。比如說flask依賴Werkzeug模塊引镊,而Werkzeug模塊的新版本有bug!
這時候,傳統(tǒng)的解決方式是使用pip freeze
弟头, 它能給出當前環(huán)境下第三方模塊和所有依賴子模塊的版本號吩抓,因此在生產(chǎn)環(huán)境就可以確保使用與開發(fā)環(huán)境相同的模塊了。這樣我就可以把pip freeze的輸出寫入到requirement.txt:
click==6.7
Flask==0.12.1
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
Werkzeug==0.14.1
這或許解決了生產(chǎn)與開發(fā)環(huán)境不一致的問題赴恨,可是它又引入了一大坨新問題疹娶!
因為,也許Werkzeug==0.14.1隱藏了一個漏洞伦连,官方發(fā)布了0.14.2版本修復(fù)了該問題雨饺,那么我不僅需要及時下載更新模塊,還得去更新requirememts.txt文件惑淳。但是那么多模塊额港,我難道要把所有更新版本都記錄下來?事實上歧焦,我本不需要記錄那么多我不關(guān)心的模塊的版本锹安,我只想記錄自己關(guān)心的核心模塊版本。
這就出現(xiàn)了一個矛盾:<確定構(gòu)建>與<不記錄所有模塊版本>之間的矛盾倚舀。
多個項目依賴不同版本的子模塊
假如我有2個項目A和B叹哭,A依賴django==1.9,B依賴django==1.10痕貌。linux默認使用全局共享的依賴包风罩,因此當我在A和B項目間切換時,需要檢測--卸載--安裝django舵稠。
傳統(tǒng)的解決方式是使用virtual environment 超升,將項目A和B的第三包隔離開。python2目前有virtualenv方案哺徊,python3目前有venv方案室琢。
而pipenv也集成了虛擬環(huán)境管理的功能。
依賴分析
先解釋一下什么叫依賴分析落追,假如我有一個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
pipenv上場啦
例子
安裝, 我們默認是安裝的python3版本哈
$ pip install pipenv
在指定目錄下創(chuàng)建虛擬環(huán)境, 會使用本地默認版本的python
$ pipenv install
如果要指定版本創(chuàng)建環(huán)境背镇,可以使用如下命令咬展,當然前提是本地啟動目錄能找到該版本的python
$ pipenv --python 3.6
激活虛擬環(huán)境
$ pipenv shell
安裝第三方模塊, 運行后會生成Pipfile和Pipfile.lock文件
$ pipenv install flask==0.12.1
當然也可以不指定版本:
$ pipenv install numpy
如果想只安裝在開發(fā)環(huán)境才使用的包,這么做:
$ pipenv install pytest --dev
無論是生產(chǎn)環(huán)境還是開發(fā)環(huán)境的包都會寫入一個Pipfile里面瞒斩,而如果是用傳統(tǒng)方法挚赊,需要2個文件:dev-requirements.txt 和 test-requirements.txt。
接下來如果在開發(fā)環(huán)境已經(jīng)完成開發(fā)济瓢,如何構(gòu)建生產(chǎn)環(huán)境的東東呢荠割?這時候就要使用Pipfile.lock了,運行以下命令旺矾,把當前環(huán)境的模塊lock住, 它會更新Pipfile.lock文件蔑鹦,該文件是用于生產(chǎn)環(huán)境的,你永遠不應(yīng)該編輯它箕宙。
$ pipenv lock
然后只需要把代碼和Pipfile.lock放到生產(chǎn)環(huán)境嚎朽,運行下面的代碼,就可以創(chuàng)建和開發(fā)環(huán)境一樣的環(huán)境咯柬帕,Pipfile.lock里記錄了所有包和子依賴包的確切版本哟忍,因此是確定構(gòu)建:
$ pipenv install --ignore-pipfile
如果要在另一個開發(fā)環(huán)境做開發(fā),則將代碼和Pipfile復(fù)制過去陷寝,運行以下命令:
$ pipenv install --dev
由于Pipfile里面沒有所有子依賴包或者確定的版本锅很,因此該安裝可能會更新未指定模塊的版本號,這不僅不是問題凤跑,還解決了一些其他問題爆安,我在這里做一下解釋:
假如該命令更新了一些依賴包的版本,由于我肯定還會在新環(huán)境做單元測試或者功能測試仔引,因此我可以確保這些包的版本更新是不會影響軟件功能的扔仓;然后我會pipenv lock
并把它發(fā)布到生產(chǎn)環(huán)境,因此我可以確定生產(chǎn)環(huán)境也是不會有問題的咖耘。這樣一來翘簇,我既可以保證生產(chǎn)環(huán)境和開發(fā)環(huán)境的一致性,又可以不用管理眾多依賴包的版本儿倒,完美的解決方案版保!
pipenv依賴分析詳解
pipenv每次安裝核心包時,都會檢測所有核心包的子依賴包义桂,對不滿足的子依賴包會做更新找筝。如果核心包package_a和package_b依賴有矛盾,比如(package_a依賴package_c>2.0, package_b依賴package_c<1.9)慷吊,則會有警告提示。
使用以下命令可以查看依賴關(guān)系:
$ pipenv graph
舉個栗子:
Flask==0.12.1
- click [required: >=2.0, installed: 6.7]
- itsdangerous [required: >=0.21, installed: 0.24]
- Jinja2 [required: >=2.4, installed: 2.10]
- MarkupSafe [required: >=0.23, installed: 1.0]
- Werkzeug [required: >=0.7, installed: 0.14.1]
numpy==1.14.1
pytest==3.4.1
- attrs [required: >=17.2.0, installed: 17.4.0]
- funcsigs [required: Any, installed: 1.0.2]
- pluggy [required: <0.7,>=0.5, installed: 0.6.0]
- py [required: >=1.5.0, installed: 1.5.2]
- setuptools [required: Any, installed: 38.5.1]
- six [required: >=1.10.0, installed: 1.11.0]
requests==2.18.4
- certifi [required: >=2017.4.17, installed: 2018.1.18]
- chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
- idna [required: >=2.5,<2.7, installed: 2.6]
- urllib3 [required: <1.23,>=1.21.1, installed: 1.22]
Pipfile
舉個栗子曹抬,它是 TOML 格式的:
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[dev-packages]
pytest = "*"
[packages]
flask = "==0.12.1"
numpy = "*"
requests = {git = "https://github.com/requests/requests.git", editable = true}
[requires]
python_version = "3.6"
我不用管子依賴包溉瓶,只會把我項目中實際用到的包放進去,子依賴包在pipenv install package
的時候自動安裝或更新。
Pipfile.lock
舉個栗子堰酿,它是JSON格式的疾宏,它包含了所有子依賴包的確定版本:
{
"_meta": {
...
},
"default": {
"flask": {
"hashes": [
"sha256:6c3130c8927109a08225993e4e503de4ac4f2678678ae211b33b519c622a7242",
"sha256:9dce4b6bfbb5b062181d3f7da8f727ff70c1156cbb4024351eafd426deb5fb88"
],
"version": "==0.12.1"
},
"requests": {
"editable": true,
"git": "https://github.com/requests/requests.git",
"ref": "4ea09e49f7d518d365e7c6f7ff6ed9ca70d6ec2e"
},
"werkzeug": {
"hashes": [
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b",
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c"
],
"version": "==0.14.1"
}
...
},
"develop": {
"pytest": {
"hashes": [
"sha256:8970e25181e15ab14ae895599a0a0e0ade7d1f1c4c8ca1072ce16f25526a184d",
"sha256:9ddcb879c8cc859d2540204b5399011f842e5e8823674bf429f70ada281b3cc6"
],
"version": "==3.4.1"
},
...
}
}
我永遠也不應(yīng)該編輯Pipfile.lock, 它只應(yīng)該由pipenv lock
生成。
pipenv的其他指令
卸載包
$ pipenv uninstall numpy
當前虛擬環(huán)境目錄
$ pipenv --venv
當前項目根目錄
$ pipenv --where
舊項目的requirments.txt轉(zhuǎn)化為Pipfile
使用pipenv install
會自動檢測當前目錄下的requirments.txt, 并生成Pipfile, 我也可以再對生成的Pipfile做修改触创。
此外以下命令也有同樣效果, 可以指定具體文件名:
$ pipenv install -r requirements.txt
如果我有一個開發(fā)環(huán)境的requirent-dev.txt, 可以用以下命令加入到Pipfile:
$ pipenv install -r dev-requirements.txt --dev
是否要將Pipfile加入到版本管理
按照上文分析坎藐,代碼和Pipfile都應(yīng)該加入版本管理,Pipfile.lock就見仁見智了哼绑,我傾向于不加入到版本管理岩馍,因為Pipfile.lock在不同的操作系統(tǒng),不同的開發(fā)階段都可能發(fā)生變化抖韩。