在〈Flake8 與 isort in VS Code〉中提到了 Python 的 linter——pylint 與 Flake8,今天就來多講一點 linter 的具體設定方法,並將 formatter 也納入。

不得不說設定 linter 與 formatter 無疑是所有 Python 專案的起手式,尤其在多人協作的時候,深入要求每個人的程式寫作習慣很難,但至少在排版風格上藉由工具能達到基本的一致

Linter 與 formatter 場景需求

最近幾個月陸續加入了兩位開發同事,開始有了彼此 code review 的需求,說真的在程式寫作習慣差異不小的前提下進行 reivew,對雙方往往都很辛苦。

而讓每一位成員的程式碼都起碼合乎 linter 的檢查與 formatter 的格式,則多少可以消彌彼此的差距,降低痛苦,減少不必要的消耗,讓我們可以把 review 的主要精力放在實作邏輯的合理性上。

所以把 linter 和 formatter 作為團隊開發的標配,再基本不過,於是我把這部分的設定寫成團隊文件供新成員參考,本文即以該文件為基礎並加上大量的文字說明。

Linter 設定

Linter 或 lint,主要功能是對程式進行靜態分析——在程式未執行情況下檢查出可能的潛在語法錯誤。我直接引用前文的描述:

Linter,簡言之就是協助你檢查程式中語法正確性的工具,各種語言都有自己的 linter,而 Python 最常見的 linter 不外乎pylintpep8(現為 pycodestyle) 和 flake8

以上 linter 都是 Python 的 package,可獨立執行、使用,這裡提及的主要是整合在 VS Code 中,讓這些選定的 linter 自動幫你檢查程式碼是否有錯誤的功能。

除了語法正確性,linter 還會檢查排版風格,比如程式是否符合 PEP 8 排版風格也是上述 linter 檢查的一環,如此才有「排版風格一致」可言。換句話說,所謂的一致,原則上指的是與 PEP 8 一致

VS Code Python linter 設定:以 Flake8 為例

無論是 pylint 或 Flake8,都必須安裝到專案使用的 Python 虛擬環境才行,因為它們本質上都是把 package 的功能整合進來,藉由 VS Code 在背景呼叫,方便你直接使用。如果沒裝的話,VS Code 會提醒你安裝,且不需要安裝額外的 VS Code 套件。

VS Code:指定的 Linter 未安裝提示VS Code:指定的 Linter 未安裝提示

VS Code 設定 Flake8 只有幾個重要的環節,依序如下:

  1. 開啟 Python Linting:Python › Linting: Enabled
  2. 開啟 Flake8 Linting:Python › Linting: Flake8 Enabled
  3. 設定 Flake8 Args(非必要):Python › Linting: Flake8 Args

以上幾個設定,都可以在 VS Code 左下角的「設定 UI(macOS 用戶可直接使用快捷鍵cmd+,開啟)」輸入關鍵字比如「Flake8」、「lint」搜尋找到。比如下圖就是 Flake8 的啟動開關:

VS Code Settings UIVS Code Settings UI

如果覺得麻煩,可以直接在 VS Code 的settings.json加入以下內容,效果相同:

1
2
3
4
5
6
"python.linting.enabled": true, // 預設值即為 true
"python.linting.flake8Enabled": true,
"python.linting.flake8Args": [
"--max-line-length=100",
"--ignore=E402,F841,F401,E302,E305,W503"
],

Linter 重點說明

  • VS Code 的 Python linting 在安裝完官方出品的 Python 套件後,預設就是開啟,所以不設定也沒差(除非你要關掉),通常第一次開啟 py 檔時會跳出提示讓你選擇想使用的 linter。
  • VS Code 支援的 Python linter 不少,有些是「通用」的,像本文的 Flake8,有些則著眼於局部的特性,比如 Mypy 著眼於 type hints,理論上它們可以全部同時開啟!只是同時使用太多 linter 可能會帶來大量的提醒,在視覺上大受干擾,建議功能相似的 linter 只需開啟一種即可。
  • Args:實際上就是幫你把 args 字串轉換成命令列上的參數,在 VS Code 呼叫這些 linter 時使用,主要是為了依需求客製化。這裡要特別講究 args 字串的格式,不同的的工具在字串格式上可能大不相同。如果打錯,則整個 linting 功能會失效,而且不會有任何錯誤提示!要特別注意。
    • 這裡的第一部分"--max-line-length=100"是設定單行字元上限,Flake8 預設的是 PEP 8 的 79 字元,從現代的角度來看可能常常容易超過,團隊討論後目前共識為 100 字元,所以必須加入 args。這可能是最常用的 args 設定。
    • 第二部分就看你想忽略哪些提醒,雖然 Flake8 相較於 pylint 已經較為寬鬆,但也不一定要全部遵守。不同的錯誤有不同的代號,在提醒的說明內會標註代號,可以依需求忽略。

使用 linter 後的效果

  1. 會出現波浪底線錯誤提示!
  2. 滑鼠移至波浪底線,會顯示錯誤訊息,包含錯誤代號。

Flake8 in VS CodeFlake8 in VS Code

Linter 功能歸納

綜上所述,通用型的 Python linter 主要功能有二:

  1. 檢查程式是否有語法錯誤並提示。
  2. 檢查程式是否符合 PEP 8 風格並提示。不過要強調一下,風格檢查不只有針對 PEP 8 而已,還有一般性的風格規範,只不過 PEP 8 是最基本也最核心的部分。

而本文著眼的是第二部分,也就是排版風格。在 linter 幫你檢測出排版風格有缺失後,接下來就輪到 formatter 登場了。

Formatter 設定

統一程式碼排版風格第一步是使用 linter,通用型 linter 會檢查(但不限於)程式碼風格上所有違反 PEP 8 的部分,發現風格不符後,接著就要用 formatter(格式化器)進行格式化!

換句話說,formatter 就是自動幫你修正這些排版問題的工具!而這個過程,就是「格式化」。

程式語法錯誤——比如使用未設定的變數——雖然也可能被 linter 檢測出並提示,但這部分的修正通常得手動為之。Formatter 只能協助處理排版風格上的缺失。

雖然修正排版一樣也可以手動為之,但是一來麻煩,二來難免會有遺漏,還是靠機器比較實在,也輕鬆得多。

VS Code Python formatter 設定:以 yapf 為例

設定 formatter 比前述 linter 單純許多,一樣在設定 UI 輸入關鍵字查詢:formatting,就能找到Python › Formatting: Provider這項設定。有三種 formatter 可選:

不同於多個 linter 可以全部開啟,formatter 功能因具有「排他性」,只能擇一使用。預設為 autopep8,這裡我們改用 Google 出品的 yapf。

Formatter 主要是為了讓你的 Python 程式碼可以最大限度遵守 PEP 8 及一般風格規範——這也是所有 Python 開發者都值得具備的良好習慣。

Formatter 一樣可以設定 args:Python › Formatting: Yapf Args。而且 VS Code 本來就可以設定「存檔時自動格式化」,但有一個限制:afterDelay 模式的自動存檔(即"files.autoSave": "afterDelay")時不會格式化,只有其他模式的自動存檔及手動存檔有效。

整體的settings.json設定如下:

1
2
3
4
5
"python.formatting.provider": "yapf",
"python.formatting.yapfArgs": [
"--style={column_limit=100}"
],
"editor.formatOnSave": true, // 不支援 afterDelay 自動存檔

不同 formatter 的差異

這些 formatter 的主要差別大概是對格式化結果「彈性」上的差異

  • autopep8

就我的使用經驗,autopep8 要求較寬鬆,輸出結果彈性最大,如果一段程式碼有兩、三種排版方式皆合乎 PEP 8 風格,則對 autopep8 而言都算是「合法」的——因為它只要求不違背 PEP 8 即可。此時它便不會再對程式碼進行修正。所以使用 autopep8,程式碼的排版風格未必能統一,我覺得這是一大缺點,個人不愛。

  • yapf

相對的,yapf 和前述 autopep8 相比,大部分的時候格式化結果都是一致的,因為它會從兩、三種都合乎 PEP 8 的風格中,讓演算法依具體情況進一步選擇其中一種作為格式化結果(儘管可能未必是你最認同的那種),我覺得更適合團隊協作。

更多介紹可參考 yapf 官方 GitHub 頁面,下面是主要部分:

YAPF takes a different approach. It’s based off of ‘clang-format’, developed by Daniel Jasper. In essence, the algorithm takes the code and reformats it to the best formatting that conforms to the style guide, even if the original code didn’t violate the style guide.

  • Black

而 Black 最嚴格,除了「單行最大字元」或「字串必須使用單雙引號(竟然連這個都要規範)」外,幾乎沒有可以客製、調整的部分,可以說絕對只有「一種」結果,嚴格程度明顯高於 PEP 8 要求,有時可能會讓人覺得太嚴格了。我將在接下來的 side project 中嘗試採用,畢竟我除了是分類控,同時也是規範控。

相關文章:試用從 VS Code Python extension 拆分的 Black、isort 套件


Args 的設定一樣要特別注意,這上述例子中就能看出,不同工具的 args 字串格式真的會有一定差異,千萬不要打錯,否則 formatter 也會無法作用。

使用 formatter 小撇步

和 linter 開啟後就會自動生效並顯示錯誤提示不同,格式化器需要你主動使用,或設定為存檔時自動格式化。以下說明「主動使用」上的方式差異與要點:

  • 格式化文件:很簡單,打開文件後按右鍵選單選擇「格式化文件」即可,也有預設的快捷鍵。這個功能我比較少直接使用,因為如果善用快捷鍵,它完全可以被「格式化選取範圍」所取代。
  • 格式化選取範圍:在反白選取範圍後按右鍵選單就會出現這個選項,沒有預設的快捷鍵。
    • 實際使用時可以不做任何選取,直接使用,會格式化插入點所在的程式行,再搭配自訂的快捷鍵,是我最常用的方式。
    • 此外,如果在文件中的空白行使用,則會「格式化整個文件」,達到上述格式化文件的效果,且更方便自行切換。

2022/05/11補充:後來都直接使用「存檔時自動格式化」了,因為整個檔案本來就都要格式化,所以選取範圍的意義並不大,而且為了防止忘記,存檔時一併執行格式化還是最適合的。

小結

Linter 和 formatter 雖然無法保證你能寫出 clean code,但至少能協助你盡可能符合 PEP 8 風格,並省下處理排版細節上的心力,讓你更專注在程式的核心部分。

有鑑於 PEP 8 在 Python 中的地位是如此無可取代,善用這些工具也成了必經之路。