跳至主要内容

環境安裝

環境安裝

哈囉,世界!

如果有人問你,什麼是程式語言,你會怎麼回答?

也許正在閱讀本書的讀者們曾經寫過別的程式語言,但我不確定你是否想過這種超級小白的新手問題,而且還得用白話文跟完全沒寫過程式的人解釋寫程式到底是在寫什麼?

電腦程式本質上是一堆 0 跟 1 的組合,透過這些 0 跟 1 的數字或訊號可以指揮電腦做一些我們想要它完成的工作,例如數學的四則運算,或是計算圓周率到底有幾位數。不過一般人應該看不太懂這些 0 跟 1 所代表的意思,所以便有人設計出一些普通人比較容易理解像是 if...else... 這種比較接近人類語言的電腦指令。這些指令最終還是會被轉換成電腦看的懂的 0 與 1,而 if...else... 這種比較容易懂的指令就稱之程式語言的語法。目前全世界的程式語言有非常多種,各有各自的用途,不同的程式語言就只是使用不同的語法在叫電腦做事而已,而本書要介紹的程式語言 Python 也是其中一種,還是目前全世界數一數二受歡迎的那種...不對,不是數一數二,就是最受歡迎的那種。

是說,大家有沒有想過為什麼程式語言有這麼多種?為什麼大家不要統一一下,都寫同一種就好了?每種程式語言都有它的用途或想要解決的問題,就像為什麼有了一般的轎車,還要另外設計跑車、貨車、休旅車或四輪驅動的越野車,就算同樣都是休旅車,不同廠商設計的休旅車也都有功能上或效能上的差異。程式語言也是類似的概念。

而「寫程式」的過程,就是使用某些工具(通常是文字編輯軟體),把這些指令存成檔案,這些檔案就像電影的劇本,演員們基本上就是照著劇本的指示演戲,電腦也是根據你寫的這些指令來完成你想要做的工作。

因為簡單、易學且功能強大以及整個大環境的帶動,不論是初學者或是專業工程師都可以使用它來完成各種工作,Python 是目前全世界最熱門的程式語言,雖然不少人可能是近幾年才聽過這個程式語言,會以為這個程式語言很新,事實上 Python 這個程式語言自 1991 年上市到現在已經超過 30 年了。Python 這個程式語言的名字,是來自於一個英國表演團體《Monty Python》,當年 Python 的發明者正在特別喜歡這個團體演出的電視影集《Monty Python's Flying Circus》,所以就把程式語言取名叫 Python。因此雖然 Python 字面上是蟒蛇的意思,但這個程式語言跟蛇或其他爬蟲類沒什麼關係。

既然本書是要教大家怎麼撰寫 Python,第一步就是先把 Python 給安裝起來!

安裝 Python

安裝 Python 的困難度不高,基本上就是到 Python 的官方網站下載安裝程式並照著指示做就行了,目前主流的作業系統,例如 Windows、macOS 以及 Linux 都有相對應的版本。是說...如果各位使用的是 macOS 跟 Linux 作業系統,可能會發現原本電腦裡就已經直接內建 Python 了,這樣還需要另外安裝嗎?系統內建安裝的 Python 通常會比現行版本稍微舊一點,如果單純就以體驗或寫一些簡單的 Python 程式的話是夠用的。之所以在作業系統內建安裝 Python 是有原因的,可能有些系統程式剛好有用到 Python。Python 的版本從早期的 2.x 版到現在的 3.x 版,除了正宗的 CPython 之外,還有其他分支的實作品版本(像是 Jython、IronPython、PyPy 等),如果不想把自己的電腦環境弄的亂七八糟,或是因為專案需要常常安裝不同版本的 Python,通常我會建議另外使用版本管理工具來建構隔離的開發環境,這裡我將使用 pyenv 做為範例。

提示

因為 Python 這個程式本身主要是用 C 語言所設計出來的,所以如果沒有特別聲明的話,平常大家在講的 Python 通常指的就是 CPython。

另外,這裡需要再特別說明一下,pyenv 本身並不是 Python,安裝 pyenv 不等於安裝 Python。你可以把 pyenv 想像成一個工具箱,在這個工具箱裡會可以放很多種工具,但這個工具箱裡如果沒有任何工具它就只是個空的盒子而已。pyenv 不只是個工具箱,還是有分隔板的工具箱,不同版本的 Python 會被安裝在不同的隔板裡,安裝套件的時候也會安裝在各自的隔板裡,透過 pyenv 就能做到簡單的切換 Python 的版本。

再次提醒,如果你的作業系統(例如 macOS)原本就有預先安裝 Python 的話,即使透過 pyenv 安裝了其他版本的 Python,原本系統內建的 Python 也建議不要移除,它存在系統裡一定是有原因的,你不會知道系統裡有哪些程式正在使用它。

安裝 pyenv

安裝 pyenv 的方式滿簡單的,只要到 GitHub 的說明頁面,照著指示複製貼上應該一下子就能安裝完成。如果是 Windows 作業系統則是另外安裝 pyenv-win

pyenv 使用簡介

安裝 pyenv 之後,我們來看幾個常用的操作。首先,可以透過 -v--version 選項得知目前 pyenv 的版本:

$ pyenv -v
pyenv 2.4.0

各位的版本不一定會跟我一樣,但只要版本號沒有差太多,功能上應該不會差太多。再提醒一次,這個並不是 Python 的版本喔,這只是 pyenv 這個工具箱本身的版本。如果想要知道目前 pyenv 這個工具箱裡有安裝了哪些工具,可以這樣做:

$ pyenv versions
* system (set by /Users/kaochenlong/.pyenv/version)

大家執行的結果可能會跟我的不一樣,上面的結果顯示目前我的 pyenv 工具箱裡只有系統內建(就是那個 system 字樣)的 Python 而已。如果想要知道現在 pyenv 有支援哪些種類的 Python,可以這樣做:

$ pyenv install --list
Available versions:
2.1.3
2.2.3
...略...
3.12.2
3.12.3
3.12.4
3.13.0b2
3.13-dev
...略...
stackless-3.7.5

這個指令會顯示一個很長的列表,上面有各式各樣的 Python,而且各種版本都有。前面沒有別的名字只有數字的,例如 2.1.3 或是 3.12.4,這指的就是正宗版的 Python,也就是 CPython。在本書撰寫的當下,現行的 CPython 版本最新的是 3.12.4

之前要安裝 Python,可能是得先到官網下載安裝檔,然後一路點下一步,現在我們有 pyenv 這個工具箱了,我們可以直接請它幫我們搞定安裝 Python 的事,只要一行指令就能搞定:

$ pyenv install 3.12.4

這裡的 3.12.4 就是在上個指令所顯示的版本列表。順利的話,等個幾分鐘應該就裝起來了。安裝完成之後再看一次版本:

$ pyenv versions
* system (set by /Users/kaochenlong/.pyenv/version)
3.12.4

這樣就多了一個 3.12.4 版本。前面的 * 符號表示現在正在使用哪個版本,如果要切換到剛剛安裝的 3.12.4 的話:

$ pyenv shell 3.12.4

再看一次版本:

$ pyenv versions
system
* 3.12.4 (set by /Users/kaochenlong/.pyenv/version)

* 符號就移到 3.12.4 了。雖然目前看起來沒問題,不過如果你把終端機關掉再重新開啟之後,pyenv 會忘記你剛剛設定的 Python 版本,可能又跳回原本系統內建的版本了。為了避免每次都得這樣手動切換,我通常會這樣做:

$ pyenv global 3.12.4

這樣等於把系統預設的 Python 版本設定為 3.12.4。這樣之後在終端機輸入 python 或是待會會介紹到的套件安裝工具 pip 的時候,就會找到這個透過 pyenv 安裝的版本,而不是原本系統內建安裝的那個版本了。

開發工具

要開始寫 Python 程式,其實只要有一個文字編輯器就行了。

目前業界有不少功能很厲害的「整合開發工具(Integrated Development Environment, IDE)」,通常 IDE 有很多便利的功能,像是偵錯、程式碼自動完成、版本控制等等。在業界比較知名也比較多人使用的應該就是 PyCharm 了。IDE 通常是由商業公司所開發,所以通常是收費軟體,但 PyCharm 有提供免費的 Community 版本,如果有學生身份到網站上申請還能免費使用全套 IDE,真的是太佛心了。

本書會把重點放在 Python 這個程式語言而不是在的開發工具上,只要學會 Python 之後,不管是 IDE 或是文字編輯軟體都可以用來撰寫 Python 程式。以新手上路來說,我會推薦大家使用 Visual Studio Code(通常簡稱 VSCode)這款文字編輯器。VSCode 不只免費,本身的功能也很完整,再搭配幾款 Python 專屬的擴充套件,用來寫 Python 的體驗還滿好的。安裝 VSCode 的方式很簡單,也是只要到它的官方網站下載合適的版本並安裝就行了。

線上開發工具?

在現在雲端服務這麼發達的現代,如果不想在自己電腦上安裝開發工具的話有辦法寫 Python 嗎?當然可以,像 Google Colab 之類的線上工具,或是在自己電腦安裝個 Jupyter Notebook 都是個不錯的選擇。如果只是想要寫些簡單的 Python 程式練練手感,或是想要跟別人分享程式碼的話,這些線上工具的確是很好的選擇。

但如果將來真的想拿 Python 當作主要的開發語言,這些線上開發工具通常會有一些限制,例如不一定能安裝自己想要的套件或是存取檔案等功能。所以如果是想要真的學好 Python 而且想用它做點東西出來的話,還是建議在自己的電腦上安裝開發工具來使用。

VSCode 擴充套件

VSCode 有非常多的擴充套件,不過如果就以寫 Python 程式來說,大概安裝 PythonPylance 這兩款擴充套件就夠用了(事實上現在只要安裝 Python 擴充套件的時候,就會自動跟著安裝 Pylance 了),只要在 VSCode 的市集上搜尋一下擴充套件的關鍵字就能找到,都是免費而且有超過千萬人次下載的套件。這些擴充套件主要是讓各位在撰寫 Python 程式碼的時候可以有比較漂亮的顏色,同時也有程式碼提示,甚至可以做到程式語法的分析,提早讓我們發現不小心寫錯的地方。

除了上面這兩擴充個套件外,為了有時候我會因為不想自己手動調整程式碼的排版或程式碼風格(Coding Style),我還會另外安裝 Black Formatter,它可以在我存檔的時候自動幫我處理格式的問題。

套件下載

安裝完這些套件,就可以開始動手寫程式了!

你的第一行程式 Hello Python

Python 以及相關的開發工具安裝完成之後,接下來就是要開始動手寫程式了。大部份的程式語言課程的第一個範例都是要大家在畫面上印出 Hello World 字樣,所以我們也不免俗的來打聲招呼。如果在 macOS 或 Linux,請打開「終端機視窗」,在 Windows 環境則是開啟「命令提示字元」或 PowerShell,然後直接輸入以下內容:

$ python -c "print('Hello Python')"

如果這是你第一次在這樣的視窗介面下敲打指令輸入的話,再次提醒一下前面的 $ 符號請不要跟著輸入,這個 $ 符號只是我用來表示這一行是個指令而已,如果跟著輸入 $ 符號反而會出現錯誤訊息。輸入完成並按下 Enter 鍵之後,如果沒有打錯字的話,應該就會在畫面上看到 Hello Python 字樣。恭喜你,你已經可以跟別人吹噓你寫過電腦程式了!

所以,剛剛這行到底做了什麼事?簡單的說,就是我們請 python 這個程式(更專業的說法叫「直譯器 Interpreter」),幫我解讀並執行 print('Hello Python') 這行 Python 語法,print() 是一個 Python 內建的函數,這個函數可以在畫面上印出 Hello Python 這幾個字。

雖然這樣直接一行就能執行程式挺簡單的,但大部份的專案都不會像這樣只有一行而已,所以通常我們會把要執行的程式碼另外存成檔案,然後再請 python 來執行它。接著請用你習慣的方式新增一個檔案,檔名要叫什麼都沒關係,但附檔名建議用 .py 結尾,所以這裡我就先建立一個名為 hello.py 的檔案並且存放在你找的到的地方(例如放在桌面上的 demo 目錄裡)。接著就用 VSCode 來編輯這個檔案,檔案內容如下:

print("Hello World")
print("哈囉世界")
print("Hello Python")

沒意外的話,你應該會在 VSCode 裡看到某些程式碼會有不同的顏色,這個是 VSCode 這款文字編輯器的功能之一。這種程式碼高亮(Syntax Highlight)可以讓我們在寫的時候更清楚有沒有不小心打錯字。這次我寫了三行,如果你想多寫幾行也可以,寫好並存檔之後,接著就要執行它了。但首先,你必須先用 cd 這個終端機指令切換到剛剛你存放 hello.py 這個檔案的所在目錄(例如桌面上的 demo 目錄),以我的電腦來說:

$ cd ~/Desktop/demo

這個指令表示會把目前的所在位置切換到我的個人帳號的桌面的 demo 目錄,各位則是看看那個檔案在什麼地方,就 cd 切換到那個目錄下。接著就來執行它:

$ python hello.py

程式碼沒寫錯或是指令沒輸入錯誤的話,應該就會在畫面上出現上面你想印出來的文字了。如果出現錯誤訊息也不用擔心,仔細看看錯誤訊息的提示,看看是沒有安裝 python 程式,還是沒有先 cd 到對的地方所以找不到 hello.py 檔案。

註解(Comment)

當你把寫好的程式交給 python 程式執行的時候,它會逐行解讀並執行你所撰寫的程式碼,不過有個特別的語法例外:

# 這是一個計算火箭發射高度的程式

def launch_height(v0, t):
g = 9.8 # 重力加速度
return v0 * t - 0.5 * g * t**2

大家暫時先不用管上面這段感覺很高科技的程式碼是什麼用途,但你看到的 # 符號,在 Python 稱之註解(Comment),正如其名,註解主要的用途是用來說明某段程式碼是在做什麼事,好讓你的同事或是將來的自己可以看的懂這段程式碼是在寫什麼。

註解的特點就是會被 Python 的編譯器給忽略而不執行它,所以有時候也常看到工程師們在開發的過程中會用註解暫時把某幾行程式碼「關掉」,像這樣:

print("hello")
# print("這行不會被執行")
print("world")

因為第 2 行加上了 # 符號,所以不會被執行。也許你會好奇,如果不想它執行刪掉它不就好了?是可以啦,但有時候我們只是想暫時先把某段程式碼先關掉,這時候註解就挺好用的。

是說,是不是每一行程式碼都要寫註解嗎?這倒也不必,照理說你在撰寫程式碼的時候最好可以讓程式碼一看就知道是什麼用途,如果真的認為程式碼有點複雜或難懂再寫註解即可。只是,回頭想想,為什麼你要寫出不容易被看懂的程式碼呢?

附檔名 .py 是什麼意思?

.py 的意思其實滿好猜的,就是 Python 的縮寫,但很多工程師可能也不知道的是,附檔名不一定要是 .py 也行。例如你可以把剛剛那個 hello.py 檔案另存成 hello.phphello.js,同樣還是可以執行的喔:

$ python hello.js

咦?為什麼 Python 可以執行 JavaScript 或其他程式語言的程式?不是這樣的,上面這個指令是把 hello.js 這個檔案的「內容」讀進來,然後請 python 來執行它,所以檔案叫什麼名字、什麼附檔名根本一點都不重要,你要把它改成 hello.jpg 也可以,你開心就好,重點是這個檔案的本質是文字型態的檔案,別被檔案的附檔名給騙了,附檔名只是讓你的作業系統知道當你用滑鼠點兩下之後應該用什麼程式來開啟它而已。

講是這樣講,我們通常不會這樣拿石頭砸自己的腳,把檔案存成 .py 附檔名除了可以享受開發工具提供給的程式碼高亮顏色以及程式碼提示等功能,而且後續在引入模組的時候也比較不會出問題。

REPL

除了使用 python -c 執行單行程式或是另外存成 .py 檔案再執行之外,直接輸入 python 指令就會進到一個特別的環境裡,你應該會在每一行的前面看到 >>> 符號。在這裡可以直接輸入程式碼,還能馬上看到執行的結果:

$ python
Python 3.12.4 (main, Jun 11 2024, 17:37:46) [Clang 15.0.0 (clang-1500.3.9.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

這個環境我們會稱它為 REPL,這是「Read-Eval-Print Loop」的縮寫,意思就是在這個環境下,Python 會「讀取(Read)」我們輸入的指令或程式碼,然後「評估(Eval)」看看是否可執行,如果可以執行就會把執行的結果「印(Print)」出來,萬一不能執行就會出現錯誤訊息。最後不管能不能執行,都會進入等待下一個指令的「迴圈(Loop)」。REPL 並不是 Python 發明的,很多程式語言都有提供 REPL 的設計,在本書如果比較簡單的程式碼範例,你會看到我直接會在 REPL 裡做示範。REPL 可以讓開發者很快的寫一些簡單的程式或是試試看某個函數的執行效果,這對新手學習者來說很有幫助。我們可以在 REPL 環境下試著輸入一些簡單的程式碼:

>>> a = 123
>>> print(a)
123
>>> 1 + 2
3

在這裡除了主動的執行 print() 函數把東西印出來之外,直接輸入 1 + 2 並按下 Enter 之後也會印出 3。為什麼不用 print() 也能印?因為 REPL 會幫你把執行的結果給「印(Print)」出來。如果不是在 REPL 的環境下,想把東西印出來還是得手動自己寫 print() 函數的。再提醒一下,前面的 >>> 標記是表示目前正處在 REPL 的環境下,而不是一般的終端機或命令提示字元,所以不用跟著敲打這幾個符號。如果要離開 REPL 環境,輸入 exit() 後按下 Enter 鍵或是按 Ctrl + D 組合鍵就可以離開。

另外,如果你有個 Python 的程式檔叫做 hello.py,內容如下:

def buy_book(title):
print(f"買了一本書:{title}")

先不用太在意這些程式碼是在做什麼,基本上就是定義了一個簡單的函數而已。剛剛有提到我們可以請 Python 幫我們執行寫好的 .py 檔案,如果你想要讓程式碼的內容在 REPL 環境下也能載入並使用的話,可以這樣做:

$ python -i hello.py

在執行的時候多加一個 -i 的設定就可以執行這個檔案並且直接進入 REPL 環境,而且在 REPL 環境下也可以使用剛剛定義好的 buy_book() 函數:

>>> buy_book("為你自己學 Python")
買了一本書:為你自己學 Python

Python 之禪

好啦,最麻煩的環境安裝跟開發工具差不多這樣就搞定了,在正式進入 Python 的程式語法介紹之前,我想請大家再次進到 REPL 環境,然後輸入 import this,按下 Enter 之後你會看到一篇有趣(?)的文章:

$ python
Python 3.12.4 (main, Jun 11 2024, 17:37:46) [Clang 15.0.0 (clang-1500.3.9.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

這是內建在 Python 裡的《Python 之禪》,作者是 Python 的核心成員之一 Tim Peters,主要內容是在講撰寫 Python 程式碼的一些指導原則。我引用維基百科上翻譯好的中文版本供大家參考:

優美優於醜陋,
明瞭優於隱晦;
簡單優於複雜,
複雜優於繁雜,
扁平優於嵌套,
稀疏優於稠密,
可讀性很重要!
特例亦不可違背原則,
即使實用比純粹更優。
錯誤絕不能悄悄忽略,
除非它明確需要如此。
面對不確定性,拒絕妄加猜測。
任何問題應有一種,且最好只有一種,顯而易見的解決方法。
儘管這方法一開始並非如此直觀,除非你是荷蘭人。
做優於不做,然而不假思索還不如不做。
很難解釋的,必然是壞方法。
很好解釋的,可能是好方法。
命名空間是個絕妙的主意,我們應好好利用它。

這意境可能有三、四層樓那麼高,目前畢竟還只是本書的第二章,如果你是新手上路的話,我猜一時之間可能還沒辦法領會。沒關係,等大家把本書看完或寫一陣子 Python 之後再回來看看這段文章,應該會有不同的感受。

既然都看到 import this 了,也順便跟大家介紹個不重要的彩蛋,在 Python 裡面有內建一個 antigravity 反重力模組,如果你像上面 import this 一樣的輸入 import antigravity 的話,放心,你不會因為失去重力而浮起來,而會看到瀏覽器自動打開並且連到某個網頁,裡面有一則漫畫,這是 Python 的開發者在 2008 年的愚人節放進來的彩蛋然後就留到現在,漫畫的內容是在講述 Python 是個簡單、容易學的程式語言。

網站連結

安裝套件

雖然 Python 程式語言本身的功能已經算是很完整,本身也有自帶很多好用的函式庫,不過有些時候我們還是會用到非官方的善心人士所撰寫的套件。在古老時代在 Python 要安裝套件其實有一點點麻煩,當時並沒有一個統一放套件的地方,有時候得先到套件作者的網站去下載壓縮檔,解壓縮之後可能得把它放在某個目錄或是再執行指令做編譯及安裝的工作,這對於初學者來說可能會有點困難。

時代在進步,技術也是,現在不用這麼辛苦了,Python 社群現在有一個叫做 PyPI(Python Package Index)的套件庫,裡面有很多實用跟不實用的 Python 套件,有興趣的話可以到 PyPI 的網站去翻看看,幾乎你想的到的套件都有人做。

網站連結

不只有套件集散地可以檢索套件,連安裝套件的過程也跟著變得簡單很多。現在安裝 Python 的時候應該都會自帶一個叫做 pip 的工具程式,這是 Python 的套件管理工具,我們到終端機視窗輸入 pip list 指令看看目前在我們的電腦裡現在安裝了哪些 Python 的套件:

$ pip list
Package Version
------- -------
pip 24.0

如果你看到跟我一樣的畫面,表示你可能跟我一樣是剛安裝好的 Python 才會這麼乾淨只有一個 pip 套件,沒錯,pip 這個套件管理工具本身也是一個套件。接下來如果要安裝其他套件,只要在終端機輸入 pip install 後面再加上套件名稱就可以了。舉個例子,我們來安裝一個很受歡迎的套件 requests ,很多人會使用這個套件來抓取網路資料:

$ pip install requests
Collecting requests
Using cached requests-2.32.3-py3-none-any.whl.metadata (4.6 kB)
Collecting charset-normalizer<4,>=2 (from requests)
...略...
Successfully installed certifi-2024.2.2 charset-normalizer-3.3.2 idna-3.7 requests-2.32.3 urllib3-2.2.1

如果你沒打錯字,而且這也是你第一次安裝 requests 套件的話,你應該會看到跟我差不多的結果,這樣就算是安裝成功了,就不用像以前要先去下載檔案再執行安裝的指令了。但如果你曾經安裝過這個套件,那麼你可能會看到類似以下的訊息:

$ pip install requests
Requirement already satisfied: requests in /Users/.../python3.12/site-packages (2.32.3)
...略...
Requirement already satisfied: certifi>=2017.4.17 in /Users/.../site-packages (from requests) (2024.2.2)

這裡的「Requirement already satisfied」表示這個套件已經安裝過了,不需要再安裝一次。如果你想要卸載這個套件,把 install 指令改成 uninstall 就行了:

$ pip uninstall requests
Found existing installation: requests 2.32.3
Uninstalling requests-2.32.3:
...略...
Proceed (Y/n)? y
Successfully uninstalled requests-2.32.3

附帶一提,因為 pip 本身也是一個套件,但它的角色比較特別,要更新 pip 的版本的話,一樣是使用 pip install pip,但再加上 --upgrade 選項:

$ pip install --upgrade pip

關於安裝套件的事其實還有一些坑,待會我們會介紹到的「虛擬環境」會再補充說明,但大家看到這裡,開發工具跟環境的介紹到這裡就算是告個段落了。如果你已經等不及要開始寫程式了,可以先跳過以下關於虛擬環境的介紹,直接進到下一章的 Python 語法介紹囉!

虛擬環境

為什麼說以下內容可以暫時先跳過?因為就算現在不知道什麼是虛擬環境也不會影響你學習 Python 程式,甚至有些人寫了好一陣子的 Python 程式也不一定有需要使用虛擬環境,就算不知道這東西的用途一樣過的很快樂,所以才說這部分的內容對新手來說可以暫時先跳過,等多做幾個專案或是需要在不同的專案中使用不同版本套件版本的時候,才會知道虛擬環境可以解決什麼問題。現在看不懂或是才剛剛新手上路的朋友可以暫時就先跳過沒關係,我是說真的!

你可能會好奇,剛剛前面介紹的 pyenv 不是虛擬環境嗎?嗯...其實不算是,pyenv 主要是用來管理各種不同版本的 Python,某種程度上的確可以幫我們做到環境隔離的效果,因為當我們執行 pip install 指令安裝套件的時候,套件會被安裝到不同版本的專屬目錄裡,但如果在同一個 Python 版本下,例如都在 3.12.4 版的時候,pip 對於套件的版本及依賴處理做的不是挺好,同一個套件在同一個 Python 版本底下它只能安裝一份,如果有需要經手好幾個不同時期的專案的話可能就會遇到一些麻煩。

附帶一提,使用 pip install 指令來安裝套件的時候,套件名稱是不分大小寫的,所以 pip install djangopip install Django 的效果是一樣的。

舉個例子,假設有兩個專案,一個是使用最新的 Django 5.0 版本,另一個比較舊的專案使用的是 Django 4.0 版本。如果我們在同一個 Python 環境下,例如 Python 3.12.4,我先安裝了 Django 4.0 版本:

$ pip install django==4.0
Collecting django==4.0
Using cached Django-4.0-py3-none-any.whl.metadata (4.0 kB)
...略...
Installing collected packages: django
Successfully installed django-4.0

接著又安裝了 Django 5.0 版本:

$ pip install django==5.0
Collecting django==5.0
Downloading Django-5.0-py3-none-any.whl.metadata (4.1 kB)
...略...
Installing collected packages: django
Attempting uninstall: django
Found existing installation: Django 4.0
Uninstalling Django-4.0:
Successfully uninstalled Django-4.0
Successfully installed django-5.0

從訊息看起來,的確是安裝成功了,但如果你仔細看上面的內容就會發現在安裝 5.0 版的過程中,4.0 版被移除掉了!再使用 pip list 指令查看版本,的確就會發現只剩下 5.0 版了:

$  pip list
Package Version
------------------ -----------
...略...
decorator 5.1.1
Django 5.0
executing 2.0.1
...略...

反之,如果這時候再裝回 4.0 版的話,5.0 版又會被移除掉,這麼如果我們需要在同一個 Python 版本下同時使用 4.0 與 5.0 版的 Django 的話就會有些麻煩,這時候虛擬環境就派上用場了。

使用 venv

在 Python 3.3 之後的版本有一個內建的 venv 模組,不用特別安裝其他套件靠它就能建立虛擬環境。舉個例子,假設我有個比較舊的專案叫做 dballz,我想為它建立個虛擬環境,我可以在終端機環境下輸入以下指令:

$ python -m venv dballz --prompt="DragonBall Z"

這裡的 -m venv 是指載入 venv 模組的意思,dballz 是虛擬環境的目錄名稱,這不是個好名字,但我故意用這名字的用意只是想跟大家說這個目錄名稱可以取自己喜歡的名字,並不是固定的指令,待會我再跟大家分享我自己工作時候比較常用的做法。另外 ,--prompt 選項是待會啟動虛擬環境的時候就會看到的字樣,可加可不加,等等你就會看到了。如果指令沒出錯的話,這時候應該你會在當前目錄看到一個名為 dballz 的目錄,這就是放置虛擬環境的目錄了。接著要來「啟動」這個虛擬環境,這裡我先使用 macOS 的作業環境示範:

# 執行 source 程式來啟動虛擬環境
$ source dballz/bin/activate

# 啟動成功的話應該會看到小括號裡字樣
$ (DragonBall Z) %

在 Linux 的指令應該也是差不多,在 Windows 環境也會依執行環境不同,啟動指令也有點不同。如果使用 Powershell 的話:

# 啟動虛擬環境
PS> dballz\Scripts\Activate.ps1

一般的命令提示字元(CMD)的話則是使用 .bat 檔案:

# 啟動虛擬環境
$ dballz\Scripts\activate.bat

雖然看起來都是黑黑的畫面,但 PowerShell 跟一般的命令提示字元是不太一樣的。

剛剛建立虛擬環境的時候,前面如果沒有特別加上 --prompt 選項的話,出現的字樣就會是這個虛擬環境的名稱,不過這個有沒有設定影響不大,只是看起來稍微比較清楚一點而已。如果想要退出虛擬環境的話,不管是 macOS、Linux 或是 Windows 都一樣,都是直接輸入 deactivate 指令:

$ (DragonBall Z) % deactivate

這樣就能退出虛擬環境了。知道怎麼啟動與退出虛擬環境之後,我們再次回到虛擬環境裡,然後執行 pip list 指令看看目前環境的套件列表:

$ (DragonBall Z) > pip list
Package Version
------- -------
pip 24.0

這時應該會發現裡面的套件列表可能跟我差不多都空空的,這是因為這個新的虛擬環境是剛剛才建立的,所以裡面什麼都沒有。接著我們在這個虛擬環境下安裝套件,例如安裝 Django 4.0 版:

$ (DragonBall Z) % pip install django==4.0
Collecting django==4.0
...略...
Installing collected packages: sqlparse, asgiref, django
Successfully installed asgiref-3.7.2 django-4.0 sqlparse-0.4.4

django==4.0 是指要安裝指定 4.0 版的 Django,安裝完成後再看一次套件列表:

$ (DragonBall Z) % pip list
Package Version
-------- -------
asgiref 3.7.2
Django 4.0
pip 24.0
sqlparse 0.4.4

看起來已安裝成功!如果這時候我退出虛擬環境後再執行 pip list 指令的話,你會發現套件列表跟剛才在虛擬環境的樣子不太一樣,這是因為每個虛擬環境是獨立的,不會影響到原本的 Python 環境。咦?那套件是裝到哪裡去了?如果你再翻一下剛剛建立的虛擬環境目錄的話,就會發現套件都直接安裝 lib 目錄裡了,我們可以透過 pip show 指令來查看指定的套件詳細資訊,以 Django 為例:

$ (DragonBall Z) % pip show django
Name: Django
Version: 4.0
...略...
Location: /tmp/dballz/lib/python3.12/site-packages
Requires: asgiref, sqlparse
Required-by:

在虛擬環境裡安裝好套件之後,我們可以使用 pip freeze 指令把目前環境安裝的套件列出來,再加上 > 符號可以把內容寫入到指定的檔案裡,通常這個檔名會叫做 requirements.txt,但要取什麼名字你可以自己決定:

$ (DragonBall Z) % pip freeze > requirements.txt
asgiref==3.7.2
Django==4.0
sqlparse==0.4.4

透過把套件列表輸出成一個檔案之後,之後可以把這個檔案交給其他人或是將來的自己,讓其他人可以在自己的電腦上安裝相同的套件。假設我再建立另一個虛擬環境(例如 Naruto),進到新的虛擬環境後,只要執行 pip install -r requirements.txt 指令,就可以把檔案裡指定的套件都安裝好:

$ (Naruto) % pip install -r requirements.txt
Collecting asgiref==3.7.2 (from -r requirements.txt (line 1))
Using cached asgiref-3.7.2-py3-none-any.whl.metadata (9.2 kB)
Collecting Django==4.0 (from -r requirements.txt (line 2))
...略...
Using cached sqlparse-0.4.4-py3-none-any.whl (41 kB)
Installing collected packages: sqlparse, asgiref, Django
Successfully installed Django-4.0 asgiref-3.7.2 sqlparse-0.4.4

就這樣一個指令就能把套件裝起來了,相當方便。而且,因為套件都是裝在虛擬環境的目錄裡,不只不會影響到原本系統的套件,如果到時候整個虛擬環境都不想要的話,只要把整個目錄刪掉就行了。雖然 venv 並沒有硬性規定虛擬環境目錄要放在哪裡也沒有規定要叫什麼名字,但如果為了方便使用,我通常會把它放在各別專案底下並取名為 .venv

# 切換到專案目錄
$ cd 某個專案目錄

# 建立虛擬環境
$ python -m venv .venv

至於要不要加上 --prompt 選項就大家可以自己決定。

其實 venv 已經算不錯用的,而且還是 Python 內建的模組,不用額外安裝套件,不過...嗯,venv 的優點是可以把套件分裝在不用的虛擬環境目錄裡,但這同時也算是缺點,就是每個虛擬環境裡都會裝一堆東西,專案一多可能就會佔用不少硬碟空間。

使用 Poetry

venv 是 Python 內建的套件,雖然功能比較陽春,但對一般的專案是滿夠用的。不過也有些人覺得它功能太陽春所以想選擇使用第三方套件來取代它,其中 Poetry 就是一個滿多人推薦的選擇。Poetry 除了可以建立虛擬環境外,還能做到安裝並管理套件版本、甚至打包、發佈套件...等等功能,基本上就是個多合一的虛擬環境加套件管理工具,功能更多、更完整,但也需要額外花點時間學一下。

安裝 Poetry

Poetry 本質上也是一個 Python 的套件,所以透過 pip install poetry 就能安裝,這樣做也真的可以運作。但是想想看,如果 Poetry 主要的目的是要用來做環境的隔離,假設我們用了某個版本或某個虛擬環境的 Python 來安裝 Poetry 的話,那切換到其他的虛擬環境之後要怎麼隔離其他的環境?所以 Poetry 官方網站上會建議我們使用另外的方式而不是透過 pip 安裝(建議使用 pipx),詳細的安裝流程請參閱官網說明。

使用 Poetry

安裝完成後,要使用 poetry 建立全新專案或是用在既有專案都可以,我們就先從建立新專案開始,使用的指令是 poetry new,例如我想建立一個新的專案叫做 dragonball-gt

$ poetry new dragonball-gt
Created package dragonball_gt in dragonball-gt

沒出錯的話,你應該會看到一個名為 dragonball-gt 的目錄,目錄裡有幾個檔案及目錄:

dragonball-gt
├─ README.md
├─ dragonball_gt
│  └─ __init__.py
├─ pyproject.toml
└─ tests
└─ __init__.py

其中 pyproject.toml 是 poetry 的設定檔,這個檔案會記錄專案的相關資訊,例如專案名稱、版本、作者、描述以及會用到的套件名稱及版本等等,README.md 則是專案的說明文件,dragonball_gt 是專案的主要程式碼目錄,tests 則是測試程式碼目錄。

如果是既有專案的話,流程跟指令會有些不同。首先需要先使用 cd 指令切換到專案目錄,接著執行 poetry init 指令,這個指令會引導你在專案的目錄裡建立一個新的 pyproject.toml 檔案。在建立的過程如果懶的填寫或還不知道要填什麼,可以先全部按 Enter 鍵選預設值就好,或是把指令改成 poetry init -n,後面加的 -n 代表 --no-interaction,也就是「不要跟我互動,先全部幫我選預設值就好」。順利完成的話,你應該會在原本的專案裡看到多了一個 pyproject.toml 檔案。如果你仔細比對這兩個指令的話,會發現 poetry init 指令做的事情也沒多複雜,主要就是幫你建立 pyproject.toml 檔案而已,所以就算不透過這個指令,直接從別的目錄複製過來,稍微調整一下內容,或甚至自己手刻一個應該也都可以正常運作。

poetry new 相比,poetry init 所產生的檔案比較少一點點,除了少幾個目錄外,還少了一個說明用的檔案 README.md,不過這些都不是大問題,需要的話這些目錄或檔案都可以自己手動建立。

這個 TOML 是「Tom's Obvious, Minimal Language(湯姆的淺顯的、極簡的語言)」的縮寫,名字本身可能對我們來說沒什麼意義,但格式內容不難懂,大概就是 key = value 的組合,有些程式語言或工具也都是採用這種格式當做設定檔。不管你的 pyproject.toml 是怎麼來的,有了這個檔案後,接著可以執行 poetry install 指令,Poetry 會根據 pyproject.toml 裡的設定,幫你安裝所需要的套件。

$ poetry install
Updating dependencies
Resolving dependencies... (0.1s)

Writing lock file

Installing the current project: dragonball-z (0.1.0)
Warning: The current project could not be installed: [Errno 2] No such file or directory: '/demo/dragonball-z/README.md'
If you do not want to install the current project use --no-root.
If you want to use Poetry only for dependency management but not for packaging, you can disable package mode by setting package-mode = false in your pyproject.toml file.
In a future version of Poetry this warning will become an error!

現在雖然我們的專案裡還沒有指定安裝任何套件,執行之後還是會幫我們產生一個 poetry.lock 檔案。

呃...是說上面這一大段警告訊息看起來有點嚇人,還說現在只是先警告而已,將來可能會變成錯誤,不過只要仔細看一下內容就知道發生什麼事了,就是少了 README.md 檔案啦。如果你是用 poetry new 建立的專案的話會順便送你一個,但我剛這個是用 poetry init 建立的,沒有這個檔案所以跳這個警告訊息,就只要手動新增一個給它就行了。

另外出現的警告訊息是關於 package-mode,這個設定是用來決定是否要把專案打包成套件。通常你要開發套件給別人用的時候可能比較會常用到這個設定,但如果只是用來寫些簡單程式或是做網站的話,基本上也只有拿 Poetry 來當套件管理工具而已,我們可以在 pyproject.toml 裡加上 package-mode = false 設定:

[tool.poetry]
...略...
package-mode = false

這樣之後警告訊息就不會再跳出來了。

大家看到這裡,不知道各位是否有其他程式語言的開發經驗?如果有的話可能也有看過這種 Lock 檔,像 JavaScript 的 package-lock.jsonyarn.lock,或是 Ruby 的 Gemfile.lock,這些 Lock 檔案用途都差不多,主要都是用來記錄目前專案裡有哪些套件,以及這些套件的版號。這麼做的好處是當你把專案交給別人或是將來的自己,只要再次執行 poetry install 指令,就可以把所有的套件都裝好,而且都是跟 Lock 檔裡的版本一樣,不會有因為套件版本不同而造成什麼神奇的問題。

新增套件

在 Python 安裝套件,通常會使用 pip install 指令,但在 Poetry 可以透過編輯 pyproject.toml 檔案再執行 poetry install,但更常使用 poetry add 指令,例如我要安裝 Django 4.0 版:

$ poetry add django==4.0

Updating dependencies
Resolving dependencies... (0.9s)

Package operations: 3 installs, 0 updates, 0 removals

- Installing asgiref (3.7.2)
- Installing sqlparse (0.4.4)
- Installing django (4.0)

Writing lock file

後面的 ==4.0 是指定版本,如果不指定的話會自動取得並安裝最新的版本。這個 poetry add 是個多合一的指令,除了幫我們安裝套件之外,安裝完成後還會自動再幫我們更新 Lock 檔,不只這樣,如果你打開 pyproject.toml 檔案,會發現裡會多了一些東西:

檔案:pyproject.toml
[tool.poetry.dependencies]
python = "^3.12"
django = "4.0"

如果想要看看目前這個專案有裝了哪些套件,可執行 poetry show 指令:

$ poetry show
asgiref 3.7.2 ASGI specs, helper code, and adapters
django 4.0 A high-level Python web framework that encourages rapid development and clean, pragmatic design.
sqlparse 0.4.4 A non-validating SQL parser.

你也許會好奇,剛才明明就只安裝了 Django,為什麼會有 asgirefsqlparse 這兩個不知道什麼用途的套件?這是因為 Django 這個套件有用到這兩個套件,如果在 poetry show 指令後面加上 --tree 選項的話就能顯示出套件之間的相依關係:

$ poetry show --tree
django 4.0 A high-level Python web framework that encourages rapid development and clean, pragmatic design.
├─ asgiref >=3.4.1,<4
├─ sqlparse >=0.2.2
└─ tzdata *

開發用的套件

在專案開發的過程中,有些套件只有開發階段會需要,但正式上線之後就不一定需要,例如說用來寫測試的 pytest,可以用來設定 Git Hook 的 pre-commit 套件,或是可以用來做程式碼自動格式化的 black 等等,這些套件跟最後上線的成品沒有直接的關連,可以不用被打包或部署到產品裡以節省資源,這些套件在使用 poetry add 安裝的時候可加上 --group 選項,把這些套件另外歸類到別的群組裡:

$ poetry add --group dev pre-commit black
Using version ^3.6.2 for pre-commit
Using version ^24.3.0 for black

Updating dependencies
Resolving dependencies... (0.7s)

Package operations: 15 installs, 0 updates, 0 removals

- Installing distlib (0.3.8)
- Installing filelock (3.13.1)
...略...
- Installing black (24.3.0)
- Installing pre-commit (3.6.2)

Writing lock file

上面這個 --group dev 你也可以用 poetry add -Dpoetry add --dev 來達到同樣的效果,但現在 Poetry 應該會提醒你這個指令已經準備要被淘汰,官方文件建議以後改用 --group dev 選項。你可以打開 pyproject.toml 會發現剛剛安裝的套件都有被加進來,只是放在不同的地方:

[tool.poetry.group.dev.dependencies]
pre-commit = "^3.6.2"
black = "^24.3.0"

其實這個 --group 選項不是只能用 dev,只要你開心這個群組名子要叫什麼名字都行,例如 --group hellokitty,但通常會定義一個比較有意義的名字,像是 devteststaging,這樣就可以把特定環境所需要的套件放在一起。

套件裝在哪裡?

是說,透過 Poetry 指令安裝的套件裝到哪裡去了?也是被裝到系統的 Python 一樣的地方嗎?不是的,Poetry 也是有虛擬環境的設計,但它不是使用 Python 內建的 venv 而是透過另一個叫做 virtualenv 的套件,這東西跟剛才介紹的 venv 的用途差不多,甚至連指令都有點像。透過 poetry env info 指令可以看到目前虛擬環境的相關資訊:

$ poetry env info

Virtualenv
Python: 3.12.4
Implementation: CPython
Path: /Users/kaochenlong/Library/Caches/pypoetry/virtualenvs/dragonball-z-eR-9doOu-py3.12
Executable: /Users/kaochenlong/Library/Caches/pypoetry/virtualenvs/dragonball-z-eR-9doOu-py3.12/bin/python
Valid: True

Base
Platform: darwin
OS: posix
Python: 3.12.4
Path: /opt/homebrew/opt/[email protected]/Frameworks/Python.framework/Versions/3.12
Executable: /opt/homebrew/opt/[email protected]/Frameworks/Python.framework/Versions/3.12/bin/python3.12

從這裡可以看的出來我的 Python 透過 virtualenv 安裝在 dragonball-z-eR-9doOu-py3.12 看起來像亂碼但又有那麼一點點規則的目錄下,在這個專案透過 Poetry 所安裝的套件都都會放在這邊,不會跟系統內建的 Python 混在一起。

虛擬環境?

雖然就照原本 Poetry 的做法簡單省事,但前面在介紹 venv 的時候也曾提到我個人比較偏好可以把虛擬環境放在各自的專案裡,比較好找,如果想要刪掉也比較好刪掉,這可以透過修改 Poetry 的全域設定來做到這件事。我們先用 poetry config --list 指令來看看目前 Poetry 的設定:

$ poetry config --list
cache-dir = "/Users/kaochenlong/Library/Caches/pypoetry"
experimental.system-git-client = false
...略...
virtualenvs.create = true
virtualenvs.in-project = false
virtualenvs.options.always-copy = false
...略...
virtualenvs.prompt = "{project_name}-py{python_version}"
warnings.export = true

在這堆資訊裡有個 virtualenvs.in-project = false 的設定,從字面上不難猜出 Poetry 預設不會幫把虛擬環境建立在專案裡,我們先把這個設定改成 true

$ poetry config virtualenvs.in-project true

設定完再回來檢查一下:

$ poetry config --list
...略...
virtualenvs.create = true
virtualenvs.in-project = true
virtualenvs.options.always-copy = false
...略...

這樣之後建立的專案就會把虛擬環境建立在專案裡了。不過這個設定只會對之後新的專案有效,剛剛我們已經做好設定的專案,得先把原本的虛擬環境給刪掉:

# 查一下現在有哪些虛擬環境
$ poetry env list
dragonball-z-eR-9doOu-py3.12 (Activated)

# 刪除指定的虛擬環境
$ poetry env remove dragonball-z-eR-9doOu-py3.12
Deleted virtualenv: /Users/kaochenlong/Library/Caches/pypoetry/virtualenvs/dragonball-z-eR-9doOu-py3.12

之後再執行一次 poetry install

$ poetry install
Creating virtualenv dragonball-z in /demo/dragonball-z/.venv
Installing dependencies from lock file

Package operations: 3 installs, 0 updates, 0 removals

- Installing asgiref (3.7.2)
- Installing sqlparse (0.4.4)
- Installing django (4.0)

這次虛擬環境就會乖乖的躺在專案的 .venv 目錄裡了,現在專案看起來大概就像這樣:

dragonball-z
├─ .venv
├─ README.md
├─ poetry.lock
└─ pyproject.toml

再次提醒,把虛擬環境改到專案裡這只是一種做法也是個人偏好,各位不一定要照著我的喜好做,你認為這樣比較方便再這麼改就好。

啟動虛擬環境

雖然 Poetry 內建有使用另一套虛擬環境的套件,但這個虛擬環境並不會自己啟動,跟之前介紹的 venv 一樣,需要有個「啟動」的儀式才行。我們可以透過 poetry shell 指令來啟動(或進入)虛擬環境:

$ poetry shell
Spawning shell within /demo/dragonball-z/.venv

$ (dragonball-z-py3.12) %

啟動成功的話,就會看到熟悉的提示,就是在前面的小括號的 (dragonball-z-py3.12),看到這個就表示我們已經進入了虛擬環境,接下來的操作就跟前面介紹的 venv 沒太大的差別,在這個虛擬環境裡安裝的套件只會影響到這個專案,不會影響到其他專案。

要退出虛擬環境的話,可以直接輸入 deactivate 或是 exit 指令:

$ (dragonball-z-py3.12) % exit
$

退出之後前面的小括號就會消失,表示我們已經回到系統的環境裡了。

如果只是想要簡單的執行某個程式的話,不一定要進到虛擬環境裡,poetry run 這個指令也能幫我們做到這件事。舉個例子,假設我平常使用的 Python 是 2.7.18,但我這個專案用的 Python 是 3.12.4:

# 原本的 Python 版本
$ python --version
Python 2.7.18

# 透過 Poetry 執行
$ poetry run python --version
Python 3.12.4

# 再次執行,又會是原本的 Python
$ python --version
Python 2.7.18

透過 poetry run 指令就能做到不需要進到虛擬環境也可以有單次在虛擬環境執行的效果。

移除套件

講到移除套件,不得不說這就是 Poetry 做的比內建的 pip 好的地方。舉個例子,如果我要安裝 Django 我會這樣做:

$ pip install django
Collecting django
Downloading Django-5.0.3-py3-none-any.whl.metadata (4.2 kB)
...略...
Installing collected packages: sqlparse, asgiref, django
Successfully installed asgiref-3.8.0 django-5.0.3 sqlparse-0.4.4

看的出來除了 Django 之外還安裝了其他兩個需要的套件,但要移除 Django 的時候:

$ pip uninstall django
Found existing installation: Django 5.0.3
Uninstalling Django-5.0.3:
...略...
Successfully uninstalled Django-5.0.3

看似好像移除了,你只要去安裝套件的目錄巡一下就會發現 Django 套件本體是刪掉了,但 Django 相關的套件都還留著,不信的話,你可以再試著重新安裝 Django:

$ pip install django
Collecting django
Using cached Django-5.0.3-py3-none-any.whl.metadata (4.2 kB)
Requirement already satisfied: asgiref<4,>=3.7.0 in ./.venv/lib/python3.12/site-packages (from django) (3.8.0)
Requirement already satisfied: sqlparse>=0.3.1 in ./.venv/lib/python3.12/site-packages (from django) (0.4.4)
Using cached Django-5.0.3-py3-none-any.whl (8.2 MB)
Installing collected packages: django
Successfully installed django-5.0.3

有看到 Requirement already satisfied 嗎?這個訊息表示 Django 的依賴套件都還在所以不用重新安裝...嗯,這算是 pip 的缺點,它不會自動移除依賴的套件,這樣會造成系統裡有一堆沒用的套件。Poetry 可以幫我們搞定這個問題,使用 poetry remove 指令可以移除指定的套件,例如:

$ poetry remove django
Updating dependencies
Resolving dependencies... (0.1s)

Package operations: 0 installs, 0 updates, 3 removals

- Removing asgiref (3.8.0)
- Removing django (5.0.3)
- Removing sqlparse (0.4.4)

Writing lock file

從訊息就看的出來 asgirefsqlparse 這兩個套件也被移除了,Nice!

語意化版本管理

在開源的世界裡,關於軟體的版本號有一套不成文的管理規範,我們就用 Python 本身來舉例,我現在用的版本是 3.12.4,這三個數字分別是:

主要版本號(Major).次要版本號(Minor).修訂版本號(Patch)

我們從最後面看回來:

  • 修訂版本號(Patch):開發者做了一些 Bug 修正,這些修正不會影響相容性,這時候會增加修訂版本號,例如從 3.12.3 變成 3.12.4
  • 次要版本號(Minor):開發者加了一些新功能,但還是跟之前的版本相容,這時候就會增加中間的次要版本號,例如從 3.11.8 變成 3.12.0
  • 主要版本號(Major):當開發者做了功能上不相容的修改的時候,就會升級主要版號。也就是說,通常只要看到這個版號有異動,就表示這個軟體的功能有很大的改動,基本上你就可以把它們當做兩個不同的產品了,不要太期待向下相容的事了,例如 Python 2.x 跟 Python 3.x 就是個例子。

這種不成文的規範叫做「語意化版本管理(Semantic Versioning)」,這可以讓開發者可以很清楚的知道這個版本的軟體有什麼變動,光從版號就知道升級到這個版本會不會有什麼問題。這個語意化版本規範雖然沒有強制規定每位開發者都要照做,但是大部分的開源專案都會遵守這個規範。

網站連結

語意化版本管理 https://semver.org/

為什麼特別提這個?因為 Poetry 會幫你管理套件的版本,而且會遵守語意化版本管理的規範,舉個例子,如果我在安裝 Django 的時候沒有指定版本號,Poetry 會幫我安裝最新的版本,但你打開 pyproject.toml 會看到這樣寫:

[tool.poetry.dependencies]
python = "^3.12"
django = "^5.0.3"

^5.0.3 的意思並不是只會安裝剛好 5.0.3 的意思,前面的 ^ 符號表示將來如果 Django 有推出新版本的話,例如 5.1.0 版,因為根據語意化版本管理的規範,昇級次要版號應該只是增加新功能,原有的功能並不會壞掉,所以執行 poetry install 指令的時候就會幫我們安裝最新的 5.1.0 版本。但如果最新版本是主版號增加的 6.0.0 的話,因為相容性的關係 Poetry 不會幫我們安裝。

除了 ^ 符號之外,還有 ~>=== 符號:

  • ~5.0.3 的意思是只會安裝 5.0.45.0.999 或更大的修正版號版本,但只要次要版號變動,例如變成 5.1.0 就不會安裝。
  • >=5.0.3 的意思應該不難猜,就是只要大於等於 5.0.3 的版本都可以安裝,包括 6.0.0 版本,這看起來就有點太刺激。
  • ==5.0.3 也是淺顯易懂,就是只會安裝剛剛好是 5.0.3 的版本,有什麼新版本都與我們無關,這是最保守的設定。

有了語意化版本管理的規範,只要一行 poetry update 指令,Poetry 可以讓我們的專案可以安裝並更新到最新的版本,但又不會因為版本升級而把原本可以正常運作的系統給弄壞掉。

也許你會想問,為什麼本來運作正常的系統需要更新版本?前輩常都會說如果東西會動就不要改它。是沒錯,但軟體版本的更新不一定是為了什麼厲害的功能,而是現有版本被發現了安全漏洞,如果不趕快升級修補的話,可能會因為這樣而造成更大的損失。

小結

沒有比較沒有傷害,跟 Python 內建的 pip 比起來,Poetry 是一個很好用的多合一套件管理工具,還能建立虛擬環境來隔離不同專案的套件避免版本衝突。其實 Poetry 還能幫我們打包並發佈套件,功能真的算是包山包海,但這有點超過本書要介紹的範圍,更多完整的功能就請大家翻閱 Poetry 官網的使用說明書了。本書後半介紹使用 Flask、FastAPI 及 Django 製作網站的時候,你可能也會看到我使用 Poetry 當做套件管理系統。

工商服務

想學 Python 嗎?我教你啊 :)

想要成為軟體工程師嗎?這不是條輕鬆的路,除了興趣之外,還需要足夠的決心、設定目標並持續學習,我們的ASTROCamp 軟體工程師培訓營提供專業的前後端課程培訓,幫助你在最短時間內建立正確且扎實的軟體開發技能,有興趣而且不怕吃苦的話不妨來試試看!