《PyCon2018》系列一:Pipenv


前言

俗話說,工欲善其事,必先利其器。我們寫代碼也是如此。在Python開發過程中,如何管理Python運行環境、package依賴關系是每個開發者都繞不過去的問題。在PyCon2018上,Kenneth Reitz介紹的Pipenv,就是用來解決這類問題的大殺器。

為何需要Pipenv?

要想明白Kennenth Reitz為何開發Pipenv,還需要從Python的package管理工具的發展歷史說起。

Python Packaging 歷史

Distutils

早期的Python提供了一個名為distutils的內置模塊。借助這個模塊,開發者可以為自己的package創建setup.py文件,再全部打包上傳到網上。當用戶想安裝這個package時,需要先從網上把文件下載下來(通常是tar包之類的),解壓,然后執行python setup.py install,即可將其安裝到Python的site-packages目錄下。

PyPI

PyPI全稱是Python Package Index,可以理解成一個集中式的索引,開發者們可以把他們的package及其metadata上傳到這上面。有了PyPI之后,其他開發者就可以從這上面下載他們需要的package,然后執行python setup.py install進行安裝。但即使這樣,也還是存在着一些問題:

  • 整個過程需要人工介入,不方便自動化
  • package都是全局安裝的,沒法同時安裝同一package的兩個不同版本
  • 過程繁瑣,用戶體驗差
Setuptools

Setuptools的出現,彌補了distutils存在的一些缺陷並提供了更加豐富的功能。Setuptools可以看作是對distutils的一系列擴展,包括支持egg安裝文件、自動化安裝工具(easy_install)以及對distutils的monkey-patch。有了easy_install,用戶想安裝某個package的時候,只需要執行easy_install <package>,工具會自動把package及其依賴(默認從官方的PyPI)下下來進行安裝。與之前的package安裝方式相比,easy_install有以下優點:

  • 更好的用戶安裝體驗
  • 絕大多數package都來自PyPI
  • 更適合自動化

至於缺點嘛,最主要的就是:沒有easy_uninstall。也就是說,你只能用easy_install安裝package,卻沒有相應的工具用來卸載。

pip

到2008年,pip以easy_install替代者的身份出現了。雖然pip大部分也是建立在setuptools的各個部件之上,但它提供了比easy_install更加強大的功能,尤其是引入了Requirements Files的概念,使得用戶可以非常方便地復制Python環境。我們可以在一個環境里執行pip freeze > requirements.txt,將當前環境的package信息全部導出,然后在新的環境里執行pip install -r requirements.txt,pip便會解析、下載並安裝這些package。當我們不需要某個package時,還可以執行pip uninstall <package>將其卸載。直到現在,pip早已成為最受Python開發者青睞的package管理工具了。

virtualenv

pip解決了單個環境下的(大部分)package管理問題,但是我們通常會在一台機器上同時開發多個項目,項目A需要Python2.7以及Flask0.9,項目B需要Python3.6以及Flask1.0,而項目C需要Python3.6以及Flask1.0.2。如此一來,我們就面臨着兩個方面的問題:

  • 對於項目A和B或者項目A和C,如何區分它們所使用的不同版本的Python以及快速切換?
  • 對於項目B和C,由於它們都使用Python3.6,安裝的第三方package都會放到Python3.6的site-packages目錄下面,那么如何區分它們所需的不同版本的Flask?

對於第一個問題,可以把所需要的Python都裝上,給它們指定不同的alias,在開發不同項目時使用不同的alias。這個方法可以工作,但是很繁瑣,而且容易出錯,如果開發者忘了使用alias或者使用了錯誤的alias,可能就會把package安裝到錯誤版本的Python下面。
對於第二個問題,單靠pip就更難解決了,因為同個版本Python的所有第三方package都在site-packages下面,沒法區分不同版本。

為了解決上述問題,我們需要一個新的工具,那就是virtualenv。virtualenv可以為每個項目創建一套隔離的Python環境,從而保證系統里不同的Python環境之間不會相互影響。在每個隔離的環境下面,再使用pip進行package管理。pip+virtualenv是目前比較主流的Python開發流程。

更進一步

前面提到,pip+virtualenv的工作方式成為了主流並延續至今。但是這種方式也有一些不足:

  • 新人(尤其是不懂Unix相關概念的新人)很難弄清virtualenv的抽象層是什么樣的
  • virtualenv的工作流程比較繁瑣,對人來說不夠自然,盡管virtualenv-wrapper的出現一定程度上緩解了這個問題
  • pip的requirements.txt過於簡單,沒法表示具體的依賴關系
  • 需要使用兩個工具(pip+virtualenv)才能完成工作,不夠便捷

下面是在只安裝了Flask的環境中執行pip freeze導出的requirements.txt。可以看到,里面包含了Flask本身及其依賴,每個package的版本都是確定的,但是沒法看出它們之間的具體依賴關系是怎樣的。試想,如果我們想使用一個開源項目,看到這樣一個requirements.txt,我們可能會誤以為這個項目直接依賴了這些packages,但實際上它只是直接依賴了Flask。

$ cat requirements.txt
click==6.7
Flask==0.12.2
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
Werkzeug==0.14.1

另一種requirements.txt的寫法就是,我們只給定需要直接依賴的package名稱,像下面這樣。使用這種方式,我們一眼就能看出項目直接依賴了哪些package。但是這里有個問題,即Flask及其依賴的版本是不確定的。如果過段時間某個依賴發布了新版本,你去新環境部署的時候pip就會給你裝上新的版本,可能會導致你的代碼沒法工作。

$ cat requirements.txt
Flask

以上就是Kenneth的演講中舉的例子,用來說明"what you want"和"what you need"之間的不匹配。

Pipfile & Pipfile.lock

為了解決"what you want"和"what you need"之間的不匹配問題,Pipfile這個新的標准被提了出來。

Pipfile被設計用來取代requirements.txt。其優點主要在於:

  • 采用TOML語法,相比requirements.txt表達能力更強
  • 默認支持兩組依賴:[packages]和[dev-packages],可以將多個requirements.txt的內容合並到一個文件,方便管理
  • 可以通過Pipfile.lock對環境進行明確、詳細地描述

Pipfile大致是這么個樣子:

[[source]]  # source這部分指定從哪里獲取package
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]  # default環境下需要的package
flask = "*"  # *表示任意版本,默認會安裝最新版本

[dev-packages]  # dev環境下需要的package

[requires]
python_version = "3.6"  # 指定python版本

通過對Pipfile進行處理,可以生成JSON格式的Pipfile.lock,包含了所有依賴及其具體的版本號,還有每個release的hash。比如下面:

{
    "_meta": {
        "hash": {
            "sha256": "8ec50e78e90ad609e540d41d1ed90f3fb880ffbdf6049b0a6b2f1a00158a3288"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.6"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "click": {
            "hashes": [
                "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
                "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
            ],
            "version": "==6.7"
        },
        "flask": {
            "hashes": [
                "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
                "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
            ],
            "index": "pypi",
            "version": "==1.0.2"
        },
        "itsdangerous": {
            "hashes": [
                "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
            ],
            "version": "==0.24"
        },
        "jinja2": {
            "hashes": [
                "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
                "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
            ],
            "version": "==2.10"
        },
        "markupsafe": {
            "hashes": [
                "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
            ],
            "version": "==1.0"
        },
        "werkzeug": {
            "hashes": [
                "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
                "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
            ],
            "version": "==0.14.1"
        }
    },
    "develop": {}
}

大家可以理解成,Pipfile只描述了你想要的package是哪些,是抽象而寬泛的,比如上面Pipfile的例子描述了我們需要Flask這個package。而Pipfile.lock則是對你在實際運行環境里需要的package以及它們所有依賴的描述,是具體而明確的,比如上面Pipfile.lock的例子描述了Flask以及其依賴的具體信息,這樣當我們想在新環境里運行我們的項目時,就可以按照這些信息來安裝所有依賴的package,確保環境的一致性。實際上,很多語言的package管理工具都支持類似Pipfile.lock這樣的Lockfile,比如Node.js的yarn和npm,PHP的Composer,Rust的Cargo以及Ruby的Bundler。

Pipenv

Kenneth Reitz開發的Pipenv,將Pipfile,pip和virtualenv整合到了一起,讓我們只使用這一個工具就可以非常方便、流暢地管理自己的Python環境。Pipenv的主要優點:

  • 可以讓你無縫使用Pipfile和Pipfile.lock,保證每個依賴的信息都是明確的
  • 提供簡潔的命令幫你操作virtualenv
  • 提供其他輔助工具,比如pipenv graph,可以顯示項目完整的依賴關系

現在Pipenv已經是Python官方推薦的工作流(package管理+virtual env管理)工具了。

Pipenv用法簡介

首先安裝pipenv:

codehub@ubuntu:~/workspaces$ pip install pipenv

然后我們創建一個workspace並切換到該目錄下(我這里是~/workspaces/pipenv_demo),創建一個新的環境:

codehub@ubuntu:~/workspaces$ mkdir pipenv_demo
codehub@ubuntu:~/workspaces$ cd pipenv_demo
codehub@ubuntu:~/workspaces/pipenv_demo$ pipenv install

如果要指定Python版本,可以使用--python參數:

codehub@ubuntu:~/workspaces/pipenv_demo$ pipenv --python /usr/local/bin/python3 install

創建完后,目錄下就會生成Pipfile和Pipfile.lock兩個文件:

codehub@ubuntu:~/workspaces/pipenv_demo$ ls
Pipfile  Pipfile.lock

下一步,我們安裝Requests:

codehub@ubuntu:~/workspaces/pipenv_demo$ pipenv install requests

安裝完畢之后,我們Pipfile就會變成下面這個樣子:

codehub@ubuntu:~/workspaces/pipenv_demo$ cat Pipfile
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
requests = "*"

[dev-packages]

[requires]
python_version = "3.6"

而Pipfile.lock則是這樣:

codehub@ubuntu:~/workspaces/pipenv_demo$ cat Pipfile.lock
{
    "_meta": {
        "hash": {
            "sha256": "8739d581819011fea34feca8cc077062d6bdfee39c7b37a8ed48c5e0a8b14837"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.6"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "certifi": {
            "hashes": [
                "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638",
                "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"
            ],
            "version": "==2018.8.24"
        },
        "chardet": {
            "hashes": [
                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
            ],
            "version": "==3.0.4"
        },
        "idna": {
            "hashes": [
                "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
                "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
            ],
            "version": "==2.7"
        },
        "requests": {
            "hashes": [
                "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
                "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
            ],
            "index": "pypi",
            "version": "==2.19.1"
        },
        "urllib3": {
            "hashes": [
                "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
                "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
            ],
            "markers": "python_version < '4' and python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.3.*' and python_version != '3.0.*'",
            "version": "==1.23"
        }
    },
    "develop": {}
}

運行pipenv graph可以將環境中的完整依賴打印出來:

codehub@ubuntu:~/workspaces/pipenv_demo$ pipenv graph
requests==2.19.1
  - certifi [required: >=2017.4.17, installed: 2018.8.24]
  - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
  - idna [required: >=2.5,<2.8, installed: 2.7]
  - urllib3 [required: >=1.21.1,<1.24, installed: 1.23]

這個時候,如果我們直接運行Python交互模式,嘗試import requests會報錯,因為還沒有激活virtual env:

codehub@ubuntu:~/workspaces/pipenv_demo$ python
Python 3.6.6 (default, Aug 25 2018, 10:34:56)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'requests'

Pipenv提供了一個非常好用的命令:pipenv shell,用於激活virtual env:

codehub@ubuntu:~/workspaces/pipenv_demo$ pipenv shell
Launching subshell in virtual environmentâ¦
 . /home/codehub/.local/share/virtualenvs/pipenv_demo-B6h7SXri/bin/activate
codehub@ubuntu:~/workspaces/pipenv_demo$  . /home/codehub/.local/share/virtualenvs/pipenv_demo-B6h7SXri/bin/activate
(pipenv_demo-B6h7SXri) codehub@ubuntu:~/workspaces/pipenv_demo$

可以看到,當激活virtual env后,命令行提示符前面多了'(pipenv_demo-B6h7SXri)',這個就相當於我們virtual env的id,表示我們現在處於這個virtual env下。再次嘗試在交互模式中import requests,成功:

(pipenv_demo-B6h7SXri) codehub@ubuntu:~/workspaces/pipenv_demo$ python
Python 3.6.6 (default, Aug 25 2018, 10:34:56)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> print(requests)
<module 'requests' from '/home/codehub/.local/share/virtualenvs/pipenv_demo-B6h7SXri/lib/python3.6/site-packages/requests/__init__.py'>

當不需要virtual env時,只需要運行exit即可:

(pipenv_demo-B6h7SXri) codehub@ubuntu:~/workspaces/pipenv_demo$ exit
codehub@ubuntu:~/workspaces/pipenv_demo$

通常我們需要把Pipfile和Pipfile.lock也加到版本管理中,以能保證同一個項目的不同開發者的Python環境保持一致。比如我們新加入了一個項目,就可以把repo clone下來,直接運行pipenv install,pipenv會自動找到已存在的Pipfile和Pipfile.lock,並根據里面的信息來安裝依賴,這樣我們就能准確無誤地復制其他人的環境了。

總結

就像Kenneth Reitz演講標題所寫的那樣,Pipenv是Python依賴管理的未來。作為一名合格的Python開發者,還是有必要學習下這個工具,提升自己的工作效率,也享受更好的工作體驗。

參考

Pipenv - The Future of Python Dependency Management


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM