1. 程式人生 > >關於Python虛擬環境與包管理你應該知道的事

關於Python虛擬環境與包管理你應該知道的事

關於我
一個有思想的程式猿,終身學習實踐者,目前在一個創業團隊任team lead,技術棧涉及Android、Python、Java和Go,這個也是我們團隊的主要技術棧。
Github:https://github.com/hylinux1024
微信公眾號:終身開發者(angrycode)

Python擁有大量的第三方庫,引用這些庫也非常方便,通過pip install就可以將這些第三方庫安裝到本地Python庫檔案目錄中,然後就可以import到專案中,極大地提升了開發者的編碼效率。
但這也帶來了一個問題:當A專案和B專案同時引用Lib庫,而A專案需要Lib版本是1.0,B專案需要Lib的版本是2.0。這樣在使用pip install

命令將Lib直接安裝到本地全域性環境中就會發生衝突,有可能會導致AB的執行環境無法同時得到滿足而執行失敗。

於是虛擬環境(virtualenv)就出現了。它的核心思想就是為每個專案提供獨立的執行環境,這樣不同的專案依賴庫就不會衝突。

0x00 使用venv+pip

1、建立venv

安裝虛擬環境也非常簡單,可以使用venv模組,例如在專案目錄中使用Python3建立一個虛擬環境

 ➜ python3 -m venv venv

於是在專案目錄中就多了一個venv的檔案目錄。這個目錄就是該專案的虛擬環境。

要使用虛擬環境就必須啟用

 ➜ source venv/bin/activate

然後在命令列中就會出現

(venv) ➜  

說明虛擬環境已經啟用。

要取消虛擬環境,使用

(venv) ➜ deactivate

2、使用pip install/uninstall

啟用虛擬環境後就可以使用pip命令安裝專案的依賴庫。例如

(venv) ➜ pip install requests

pip會將requests安裝到venv/lib/python3.7/site-packages目錄中。

要解除安裝依賴庫

(venv) ➜ pip uninstall requests

3、pip list

要檢視venv中安裝了哪些依賴庫

(venv) ➜ pip list
Package        Version 
-------------- --------
beautifulsoup4 4.7.1   
certifi        2019.3.9
requests        2.21.0    

4、pip freeze

使用pip freeze可以將依賴庫列表儲存到一個requirements.txt檔案,可以讓其他專案的協作者可以快速地建立專案的執行環境。

(venv) ➜ pip freeze > requirements.txt

於是要安裝專案依賴

(venv) ➜ pip install -r requirements.txt

這樣就可以建立一個統一的執行環境了。

5、venv+pip方案存在的問題

到目前為止一切都執行良好,這應該是不錯的解決方案了吧。實際上這種方案我一直都在使用,目前也是,也並沒有遇到什麼問題。
直到有一天有人說使用venv+pip也不能保證我的執行環境的一致性和可靠性。
他的理由也很簡單,專案的庫環境是依賴requirements.txt,而requirements.txt中庫有沒有指定版本號,這樣在使用pip install -r requirements.txt的時候也會導致安裝不確定的版本。

例如

beautifulsoup4  
certifi        
requests        

當然可以為每個庫指定確切的版號來解決,於是

beautifulsoup4 4.7.1   
certifi        2019.3.9
requests        2.21.0 

這樣做可以解決上面的問題,但是如果某個第三方庫修補了一個漏洞,要使用pip install --upgrade更新這些依賴庫的話,就不是那麼容易了。

還有一個問題requirements.txt中的依賴庫也有可能會出現版本號衝突。 情況是這樣:

ALib -> sublib_1.0
BLib -> sublib_2.1

ALibBLib同時依賴於sublib,但它們依賴的版本不一樣,最終在使用pip install -r requirements.txt也可能會因為依賴庫中的子依賴庫版本不相容而導致專案執行失敗。

於是就出現了pipenv

0x01 pipenv

說實話在這之前我一直都是使用venv+pip,當看到上面的問題之後,我覺得有必要了解一下pipenv

1、安裝pipenv

➜ pip install pipenv

一旦安裝完成就會引用兩個檔案,PipfilePipfile.lock。前者是用於取代requirements.txt檔案的,後者則是用於保證依賴庫的確定性和一致性。

實際上,pipenv命令底層也是封裝了pipvenv的操作,並提供了一套簡單的互動命令。

建立虛擬環境

要在專案目錄下(如pipenvdemo)使用

➜ pipenv shell

當出現類似以下資訊時說明虛擬環境建立並激活成功,當然還可以使用--two--three引數指定使用Python2還是Python3來建立,也可以使用確定的Python版本號如--python3.7

➜ pipenv shell --three

➜ pipenv shell --python3.7

由於我電腦本地Python環境是3.7,所以這裡直接預設它會指向本地預設版本

Creating a virtualenv for this project…
Pipfile: /Users/mac/PycharmProjects/pipenvdemo/Pipfile
Using /usr/local/opt/python/bin/python3.7 (3.7.3) to create virtualenv…
⠋ Creating virtual environment...Already using interpreter /usr/local/opt/python/bin/python3.7
Using base prefix '/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7'
New python executable in /Users/mac/.local/share/virtualenvs/pipenvdemo-fHPp2Wq9/bin/python3.7
Also creating executable in /Users/mac/.local/share/virtualenvs/pipenvdemo-fHPp2Wq9/bin/python
Installing setuptools, pip, wheel...
done.

✔ Successfully created virtual environment!
Virtualenv location: /Users/mac/.local/share/virtualenvs/pipenvdemo-fHPp2Wq9
Creating a Pipfile for this project…
Launching subshell in virtual environment…
 . /Users/mac/.local/share/virtualenvs/pipenvdemo-fHPp2Wq9/bin/activate

同時在專案目錄下(如pipenvdemo)生成一個Pipfile檔案。類似的,命令列也會出現以下樣式

(pipenvdemo) ➜ pipenvdemo

其中Pipfile的內容為

[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]

[requires]
python_version = "3.7"

2、pipenv install

當要安裝第三方庫當時候就直接使用pipenv install命令,以下指定flask版本號進行安裝。

➜ pipenv install flask==1.0.1

也可以不指定版本號

➜ pipenv install flask

終端會出現類似以下資訊

Installing flask==1.0.1…
Adding flask to Pipfile's [packages]…
✔ Installation Succeeded
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
⠙ Locking...

還可以使用--dev引數用於安裝開發環境依賴的庫

➜ pipenv install pytest --dev

在專案目錄又生成了另一檔案Pipfile.lock,並且Pipfile檔案也會更新

[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
pytest = "*"

[packages]
flask = "==1.0.1"

[requires]
python_version = "3.7"

看到在[packages]標籤下列出了依賴庫flask和版本號,上面還有一個[dev-packages]是用於標示開發版本依賴庫,這樣可以用於區分生產環境和開發環境。只有在命令中使用了--dev引數才會安裝[dev-packages]下列出的依賴庫。

Pipfile.lock檔案的內容要豐富一些,主要是包含了依賴庫(包括子依賴庫)的版本號以及檔案hash的資訊,從而可以保證依賴庫是確定的。

{
    "_meta": {
        "hash": {
            "sha256": "3bba1f1c4de8dd6f8d132dda17cd3c720372a1eed94b9523b9c23013e951c8ae"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.7"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "click": {
            "hashes": [
                "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
                "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
            ],
            "version": "==7.0"
        },
        "flask": {
            "hashes": [
                "sha256:cfc15b45622f9cfee6b5803723070fd0f489b3bd662179195e702cb95fd924c8",
                "sha256:dbe2a9f539f4d0fe26fa44c08d6e556e2a4a4dd3a3fb0550f39954cf57571363"
            ],
            "index": "pypi",
            "version": "==1.0.1"
        },
        "itsdangerous": {
            "hashes": [
                "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
                "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
            ],
            "version": "==1.1.0"
        },
        "jinja2": {
            "hashes": [
                "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
                "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
            ],
            "version": "==2.10.1"
        },
        "markupsafe": {
            "hashes": [
                "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
                "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
                "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
                "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
                "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
                "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
                "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
                "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
                "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
                "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
                "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
                "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
                "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
                "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
                "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
                "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
                "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
                "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
                "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
                "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
                "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
                "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
                "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
                "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
                "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
                "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
                "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
                "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
            ],
            "version": "==1.1.1"
        },
        "werkzeug": {
            "hashes": [
                "sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c",
                "sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6"
            ],
            "version": "==0.15.4"
        }
    },
    "develop": {}
}

3、pipenv lock

假設我們要把專案釋出到生產環境了,這時就要使用pipenv lock命令

➜ pipenv lock

終端會出現類似如下資訊

Locking [dev-packages] dependencies…
✔ Success!
Locking [packages] dependencies…

這個命令會建立或更新Pipfile.lock檔案,需要注意的是我們不應該手動修改此檔案。

然後就可以在生產環境中使用以下命令恢復環境

➜ pipenv install --ignore-pipfile

指定--ignore-pipfile引數的意思是,只要恢復Pipfile.lock的列表的依賴庫和子依賴庫。如果要恢復開發環境中的依賴庫,即安裝[dev-packages]下面的依賴庫,可以使用

➜ pipenv install --dev

以上就是pipenv的簡單用法。

4、pipenv graph

現在再來看看前面提到的問題

ALib -> sublib_1.0
BLib -> sublib_2.1

AB模組都依賴同一個庫,但依賴庫的版本號不一樣。這時使用pipenv install就出現類似以下資訊

Warning: Your dependencies could not be resolved. You likely have a mismatch in your sub-dependencies.
  You can use $ pipenv install --skip-lock to bypass this mechanism, then run $ pipenv graph to inspect the situation.
Could not find a version that matches sublib=1.0,sublib=2.1

可以使用

➜ pipenv graph

檢視當前專案依賴庫及其子依賴庫資訊,以樹層級方式進行展示

Flask==1.0.1
  - click [required: >=5.1, installed: 7.0]
  - itsdangerous [required: >=0.24, installed: 1.1.0]
  - Jinja2 [required: >=2.10, installed: 2.10.1]
    - MarkupSafe [required: >=0.23, installed: 1.1.1]
  - Werkzeug [required: >=0.14, installed: 0.15.4]
pytest==4.6.2
  - atomicwrites [required: >=1.0, installed: 1.3.0]
  - attrs [required: >=17.4.0, installed: 19.1.0]
  - importlib-metadata [required: >=0.12, installed: 0.17]
    - zipp [required: >=0.5, installed: 0.5.1]
  - more-itertools [required: >=4.0.0, installed: 7.0.0]
  - packaging [required: Any, installed: 19.0]
    - pyparsing [required: >=2.0.2, installed: 2.4.0]
    - six [required: Any, installed: 1.12.0]
  - pluggy [required: >=0.12,<1.0, installed: 0.12.0]
    - importlib-metadata [required: >=0.12, installed: 0.17]
      - zipp [required: >=0.5, installed: 0.5.1]
  - py [required: >=1.5.0, installed: 1.8.0]
  - six [required: >=1.10.0, installed: 1.12.0]
  - wcwidth [required: Any, installed: 0.1.7]

本例中我們安裝了flaskpytestpipenv graph命令展示了它們各自需要的依賴。還可以加上--reverse引數

➜ pipenv graph --reverse

使用這個引數可以很方便的分析出衝突的庫。

atomicwrites==1.3.0
  - pytest==4.6.2 [requires: atomicwrites>=1.0]
attrs==19.1.0
  - pytest==4.6.2 [requires: attrs>=17.4.0]
click==7.0
  - Flask==1.0.1 [requires: click>=5.1]
itsdangerous==1.1.0
  - Flask==1.0.1 [requires: itsdangerous>=0.24]
MarkupSafe==1.1.1
  - Jinja2==2.10.1 [requires: MarkupSafe>=0.23]
    - Flask==1.0.1 [requires: Jinja2>=2.10]
more-itertools==7.0.0
  - pytest==4.6.2 [requires: more-itertools>=4.0.0]
pip==19.1.1
py==1.8.0
  - pytest==4.6.2 [requires: py>=1.5.0]
pyparsing==2.4.0
  - packaging==19.0 [requires: pyparsing>=2.0.2]
    - pytest==4.6.2 [requires: packaging]
setuptools==41.0.1
six==1.12.0
  - packaging==19.0 [requires: six]
    - pytest==4.6.2 [requires: packaging]
  - pytest==4.6.2 [requires: six>=1.10.0]
wcwidth==0.1.7
  - pytest==4.6.2 [requires: wcwidth]
Werkzeug==0.15.4
  - Flask==1.0.1 [requires: Werkzeug>=0.14]
wheel==0.33.4
zipp==0.5.1
  - importlib-metadata==0.17 [requires: zipp>=0.5]
    - pluggy==0.12.0 [requires: importlib-metadata>=0.12]
      - pytest==4.6.2 [requires: pluggy>=0.12,<1.0]
    - pytest==4.6.2 [requires: importlib-metadata>=0.12]

5、其它命令

刪除一個依賴包

➜ pipenv uninstall numpy

刪除所有依賴庫

➜ pipenv uninstall --all

➜ pipenv uninstall --all-dev

--all-dev是指定刪除所有開發環境的依賴。

檢視venv目錄路徑

➜ pipenv --venv

/Users/mac/.local/share/virtualenvs/pipenvdemo-fHPp2Wq9

檢視當前專案路徑

➜ pipenv --where

/Users/mac/PycharmProjects/pipenvdemo

0x02 總結一下

從目前使用pip+venv的包管理工具遇到的問題出發,提到這種方式遇到的問題是依賴庫以及它們的子依賴庫又可能會出現版本衝突以及管理的問題。於是有人提出新的包管理工具pipenv。它有兩個核心檔案PipfilePipfile.lock檔案,前者用於指定當前專案中的直接依賴庫資訊,而後者則指定了依賴的依賴的庫資訊,通過依賴的hash值以及版本號來確定依賴庫的一致性。

0x03 參考引用

  • https://docs.python.org/3/tutorial/venv.html
    虛擬環境與包管理官方文件
  • https://realpython.com/pipenv-guide/
    A Guide to the New Python Packaging Tool
  • https://www.kennethreitz.org/essays/a-better-pip-workflow
    pipenv作者關於使用pip與virtualenv問題的描述
  • https://packaging.python.org/tutorials/managing-dependencies/#managing-dependencies
    依賴庫管理官方文件