前言

這篇是用來推廣 pipenv 的使用的。

以下文章適用 Linux/Unix 系統,如果你是 Windows 用戶的話,一些指令請自行替換。

先假設你已經會 python 了。

至少,你看得懂

1
2
# hello.py
print("Hello world!")

假設你把上面的程式碼儲存成 hello.py 了。你知道(也必須知道)怎麼使用 terminal,或是終端機,去運作他。

1
2
3
$ python hello.py

# Output: Hello world!

python 是如此的簡單迷人。

然而在使用了 python 一陣子之後,肯定會出現以下的情形吧。

假設有兩個專案,簡單區分,叫做 project_old 跟 project_new。檔案結構如下:

1
2
3
4
5
.
├── project_new
│   └── new.py
└── project_old
    └── old.py

[email protected] 代表著使用了版本 3.7 的 python。

project_old資料夾裡有 old.py,使用了 [email protected] ,並且導入了 [email protected](當然沒有這個package,我杜撰的,一時想不到有什麼好舉例的)。 在 old.py 中使用了一個 deprecated_function 。知道這個 function 會在一個版本後被淘汰,所以只有在 1.2 版本前才能使用。而這個 function可能出現很多次,又或者你深知他其實比較好用,因次你選擇讓這個檔案停留在 [email protected] 的版本。

1
2
3
4
# old.py python 3.7
import package # package version 1.1
foo = package.deprecated_function() # Only support until 1.2
print("foo={}".format(foo))

然而同時有另一個專案,就叫做project_new,使用了 [email protected],因為他有一個新的 new_function 可以使用。並且因為使用了新的 format 方法,需要使用較高版本,假設使用 [email protected]

1
2
3
4
# new.py python 3.10
import package # package version 1.5
bar = package.new_function()
print(f"bar={bar}") # New format feature after python 3.8

我們知道 python 的 module 是利用 pip 進行安裝的。一開始為了 project_old 安裝了 [email protected] ,並且又安裝了 [email protected]。但為了 project_new,把 python 升級成 [email protected]。在大部分情況下,python 是幾乎能向前相容的,除非使用了被淘汰的特性(通常會寫在更新日誌中),因此升級成較高版本的 python 是合理的。

但 module 就不一樣了。module並沒有被設計為向前相容(當然大多數會為了向前相容而設計,但這就要看開發者的心情了,不是嗎)。因此在安裝的時候就不可能只安裝最新的版本。

打個比方,numpy 就為了不同的 python 有不同的版本。[email protected] 就只支援 [email protected]

所以上述的例子,不可能利用升級 module 的手段進行兼容,也就是 pip install package==1.5 會使得 project_old 變得不可運行。

這就是環境在同一個的弊病。

我的環境簡直髒爆了

那有沒有什麼解決方案?

有的,而且很多。在現今環境及版本管理幾乎是主流的現在,python 也擁有非常多的版本管理生態系。

  • virtualenv:老牌 pip 環境管理工具
  • venv:官方的環境管理工具
  • pyenv:管理多 python 版本的工具
  • pipenv:本日主角

前三者就不多說明了,有興趣的可以自行搜尋文件。

本日主役:pipenv

pipenv做了什麼?

pipenv 整合了

  1. python 版本管理(透過 pyenv 完成)
  2. pip 環境管理(類似 virtualenv)

並且被官方支援更新。

接著透過實際操作來了解 pipenv 到底做了什麼。

Install

首先安裝 pipenv 來直接開始吧。打開你的終端機,或是 terminal 先。

如果你已經電腦有 python 的話,不介意再多安裝一個 module 吧。

1
$ pip install pipenv

又或者你是個 mac 使用者,想使用萬能的 brew 安裝也行。

1
$ brew install pipenv

不管是哪種方法,安裝完之後確認有沒有安裝成功。

1
2
$ pipenv --version
# pipenv, version 2022.4.21

出現了安裝版本就是安裝成功了。

Usage

我安裝好了,我該怎麼使用它?

首先,你要有你的 project 資料夾。pipenv 會認定 project 的資料夾就是一個環境。

1
2
$ mkdir project
$ cd project

接著,要開始初始化環境了。直接在 project 資料夾下輸入

1
$ pipenv --python 3.10 # 指定版本

等待完成,環境就建立好了,非常的簡單。 如果你的電腦沒有該 python 版本的話,pipenv 不會初始化成功。也就是電腦的版本是 python3.8 的話,就會需要已經安裝過之後,才能在 pipenv 中指定版本。 如果有需要任意切換版本的話,請愛用 pyenv 來輔助。安裝完之後可以直接搭配 pipenv 使用,也就是可以任意指定版本,pyenv 會詢問是否要安裝。

接著安裝任意套件吧,安裝步驟跟 pip 高度相似。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ pipenv install numpy
Installing numpy...
Adding numpy to Pipfile's [packages]...
✔ Installation Succeeded
Pipfile.lock not found, creating...
Locking [dev-packages] dependencies...
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
✔ Success!
Updated Pipfile.lock (a6e810)!
Installing dependencies from Pipfile.lock (a6e810)...
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.

但只有一點不太一樣,如果要列出 project 中安裝了什麼套件的話,不能使用 pipenv list 而是使用 pipenv graph

接著在環境中執行程式吧,將下列程式碼儲存成 main.py

1
2
3
4
5
# main.py

import numpy as np
foo = np.array([1,2,3])
print(f"foo={foo}")

直接執行。

1
2
$ pipenv run python main.py
# foo=[1 2 3]

可以看出來,原本只要輸入 python main.py 就能執行的程式,為了要讓他執行在環境內,必須要加上 pipenv run。如果沒加的話,就是跑在全域環境中喔。

此外如果覺得加上 pipenv run 太煩,可以試試看 pipenv shell。 這個指令會讓 shell 進到環境中,在環境內部直接執行 python main.py 也算是在環境內部執行的。如果要離開環境,直接 exit 就行了。

Pipfile

在建立環境後,會有個 Pipfile 的檔案,內容如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
numpy = "*"

[dev-packages]

[requires]
python_version = "3.10"

[package] 記錄著安裝的套件,而 requires 則紀錄了 python 的版本。

與 Pipfile 同時出現的還有 Pipfile.lock。這是用來檢查安裝套件的 sha256。基本上這是用來避免套件不一致。

試想,安裝 pip module 時,其實是連結到網路的某個位置,尋找相同名字進行下載。如果 dns 遭到竄改,下載到含有惡意代碼的 module,這時 lock file 就會發揮它的作用,來避免下載到非預期的套件。

pipenv對我做了什麼?

首先,python 的環境建立在修改python以及pip的路徑

也就是,當建立的一個環境之後,環境內的 python 以及 pip 會被映射到環境指定的位置。

簡單測試一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ where python
python not found

$ where pip
/usr/local/bin/pip

$ pipenv shell
Launching subshell in virtual environment...
 . ~/.local/share/virtualenvs/<project>-avKpqtI9/bin/activate

$ . ~/.local/share/virtualenvs/<project>-avKpqtI9/bin/activate

$ where pip
~/.local/share/virtualenvs/<project>-avKpqtI9/bin/pip
/usr/local/Cellar/pipenv/2022.4.21/libexec/tools/pip
/usr/local/bin/pip

$ where python
~/.local/share/virtualenvs/<project>-avKpqtI9/bin/python

可以看到在 pipenv shell 之前,pip 的位置是系統設定的,以及 python 根本沒有其對應位置(我使用 mac,預設就是 python3)。

而經過環境修改後,pip 以及 python 都被替換成環境內的 binary file 了。

我要怎麼分享我的環境?

將你的程式碼以及 Pipfile 包在一起發送給其他人,當其他人得到你的 Pipfile 時,直接輸入

1
$ pipenv

就可以安裝回環境了,是不是很方便呢。

在 github 上,若是有 project 有 Pipfile 的話,表示他使用 pipenv 管理環境,因此可以很簡單的複製環境。

練習

建立一個自己的專案,使用 pipenv 安裝需要的套件,並且自己執行看看。