Python 工具鏈讓你寫的代碼更規範

從以前一個不會敲 Python 代碼的小白,到現在敲 Python 代碼賊溜的老鳥的過程中,除了通過大量的學習、實踐讓自己現在更容易寫出 Pythonic 的代碼外,大部分時間裏還是多虧着有着許多好用的工具輔助我,去檢查我代碼中的問題,幫助我寫出更規範的代碼。

學習和實踐新知識可能對於大多數人來說都沒有統一的定式,但使用工具至少可以讓我們大家風格統一也更容易和他人協作共事。因此無論對於剛入門的小白還是正在成長的新人來說,會使用能輔助你寫出更規範的代碼的工具也是十分有必要的。

black

隨着 PEP8 規範的提出,Python 社區也有了自己的一套關於 Python 代碼的規範指導。但我相信,大多數人其實並不會條分縷析地去瀏覽或記下規範裏的大多數內容,因爲這些內容往往都過於瑣碎,以至於在實際開發中我們很難做到去一邊寫代碼一邊去檢查我們是否合乎 PEP8 規範。所以最好的方法就是藉助格式化工具幫我們完成這一機械的操作。

早期 Python 著名的格式化工具的有 autopep8 和 Google 的 yapf,但在實際過程中或多或少需要一些配置。

如果你使用過 Go 語言,那可能會對 Go 自帶的 gofmt 讚歎不已。因爲它既不會要求你進行多餘的配置,還能在每次保存代碼時強制幫你進行格式化,所以無論和你協作的同事們代碼風格和個人習慣如何,它都能將其轉換成符合工程的、規範的樣式。

而 black 就是類似於 gofmt 這樣號稱 「不妥協」(uncompromising)的 Python 格式化工具,它讓我們將更多的時間和精力放在編寫代碼上,而無需過度關注于格式化的配置細節。因此當我們在團隊協作中只要是使用了 black 工具之後,大部分情況下代碼看起來都是十分統一的。

比如說下面這一段代碼(來源於官方示例):

1def very_important_function(template: str, *variables, file: os.PathLike, engine: str, header: bool = True, debug: bool = False):
2    """Applies `variables` to the `template` and writes to `file`."""
3    with open(file, 'w') as f:
4        ...
5
6

由於 very_important_function 已經加上了類型註解,因此在現有的部分參數所佔寬度的基礎上又擴展了一些,所以如果我們顯示器的寬度不夠時,就需要橫向拖動才能查看參數信息;而最好的辦法就是採取豎向的方式進行排列,便於我們能自上而下的一覽無遺。所以使用 black 之後上述代碼將會是這樣:

 1def very_important_function(
 2    template: str,
 3    *variables,
 4    file: os.PathLike,
 5    engine: str,
 6    header: bool = True,
 7    debug: bool = False,
 8):
 9    """Applies `variables` to the `template` and writes to `file`."""
10    with open(file, "w") as f:
11        ...
12
13

可以看出,經過格式化後的函數其參數層次分明地對齊,可讀性大大的增強了;並且如果需要對函數中的參數進行註釋或增加,直接新增或減少一行即可,絲毫不會額外調整其他參數的位置。

儘管說 black 還是一個實驗項目,但它早已頻繁被使用在一些主流的 Python 開源項目中,比如 Pandas、SQLAlchemy、Pytest 等,所以就算在實際開發中我們一樣可以沒有顧慮地使用它。

使用 black 的方式很簡單,通過 pip install black 安裝到你的 Python 環境中,然後在代碼中運行:

1black <項目文件夾 / 源碼文件夾 / .py 文件等>
2
3

black 命令行也提供了一些參數項(但不會太多),我們大部分時候即使我們使用默認的也是足夠了的,具體我們可以參考 black 的官方文檔。

isort

isort 是一個名爲 PyCQA(Python Code Quality Authority)組織所維護的代碼質量工具中的其中一個開源項目,它同樣是用來對代碼進行格式化。但不同於 black 的是,它主要用來格式化我們 import 的庫或模塊。

Python 社區的生態一直都是十分豐富,所以在開發項目的過程中我們往往會使用到多個庫或同一個庫中的多個模塊,但在缺乏規範的情況下,被導入的部分可能會凌亂、無組織地分散在我們的代碼中,所以當我們復現代碼時很可能就會因爲忘記導入某些模塊而報錯。

就像官方給出的示例代碼一樣:

 1from my_lib import Object
 2import os
 3from my_lib import Object3
 4from my_lib import Object2
 5import sys
 6from third_party import lib15, lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11, lib12, lib13, lib14
 7import sys
 8from __future__ import absolute_import
 9from third_party import lib3
10
11print("Hey")
12print("yo")
13
14

上述的代碼存在了多種 import 風格,比如同一模塊下分別導入的部分各佔一行、同一模塊下導入的部分只站一行等;並且還存在重複導入的情況。因此要麼我們就乾脆每個導入的部分只佔一行,要麼就是來自同一模塊下的導入部分都連接在一起。默認情況下 isort 會是後者:

 1from __future__ import absolute_import
 2
 3import os
 4import sys
 5
 6from third_party import (lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8,
 7                         lib9, lib10, lib11, lib12, lib13, lib14, lib15)
 8
 9from my_lib import Object, Object2, Object3
10
11print("Hey")
12print("yo")
13
14

使用了 isort 之後它會將我們每個 .py 文件下中 import 部分的代碼進行規整,同時它會按照內置庫(及字母順序)、第三方庫以及本地自有模塊的次序對我們所導入的部分進行排列。這樣我們的代碼不僅看起來清爽了許多,同時也能準確定位到我們已經導入的部分有哪些。

isort 不會像 black 那樣「執拗」,而是也給你儘可能地預留了一些可供自定義的配置項,以便能滿足不同風格或團隊規範的需要。但對它的使用和 black 一樣簡單,只需要將其安裝到我們的環境中後,直接調用即可:

1isort <項目文件夾 / 源碼文件夾 / .py 文件等>
2
3

靜態檢查工具

由於像 Python、JavaScript 這些動態類型的解釋型語言,不具備 Java、Go 這樣靜態類型的編譯型語言在編譯期時的檢查機制,因此當一個項目中的代碼量過多時,就會造成「動態一時爽,重構火葬場」的局面。

儘管 Python 社區推行了 PEP484 提案並讓 Python 擁有了類型註解的能力,但不正確的使用依舊是很難達到靜態語言編譯檢查的那種效果。所以,一些靜態類型檢查工具也就應運而生,以便能及時發現我們代碼中的問題

目前關於 Python 的靜態檢查工具有許多,知名的有那麼幾個:

以上都是使用人數多的、並且質量都較爲上乘靜態檢查工具,可以根據自己或團隊的實際需要來選擇;不過 flake8 和 mypy 通常都算是一個正規項目中的標配了,這裏我就只以 flake8 爲例。

和前面兩個的工具一樣,先通過 pip install flake8 命令安裝之後再使用:

1flake8 <項目文件夾 / 源碼文件夾 / .py 文件等>
2
3

大部分情況下,flake8 會根據默認的一些規範對代碼進行檢查,默認是依照 pyflake 的錯誤(或違規)碼錶來進行檢查,你可以在 flake8 官方 《Error/Violation Codes》 一章中瀏覽到完整的碼錶;當然我們也可以通過特定的命令行參數來指定檢查的事項或忽略掉的事項:

1flake8 --select E121 example.py
2
3

這裏就只會選擇檢查 E121 這項來自於 pycodestyle 碼錶中的一項,如果代碼導入的模塊或包沒有縮進或懸掛,那 flake8 就會在檢查代碼之後拋出信息:

1example.py:5:9: E121 continuation line under-indented for hanging indent
2
3

這裏就對應着 example.py 文件裏的第 5 行到第 9 行代碼,我們直接就可以根據 flake8 的結果精準修改檢驗不通過的部分即可。

工具鏈整合

上述說的幾個工具,大部分情況都能通過 IDE、腳本、Git Hook 等途徑得到有效整合。通常情況下我都會在每次提交代碼到倉庫前預先分別使用 isort、black 以及靜態檢查的 flake8。

由於 black 的情況會比較特殊,像 isort 這樣的格式化工具處理過後的代碼都會 black 再次格式化,因此 black 也給出了兼容其他工具的配置項共同選項,盡最大程度的和其他工具共存。根據 black 官方的建議,通常我們可以放在某些配置文件中,比如 pyproject.tomlsetup.cfg 等,內容如下:

1multi_line_output = 3
2include_trailing_comma = True
3force_grid_wrap = 0
4use_parentheses = True
5ensure_newline_before_comments = True
6line_length = 88
7
8

同理,flake8 也是如此:

1max-line-length = 88
2extend-ignore = E203, W503
3
4

因此我們可以將以上內容都寫在一個配置文件中,這裏我選的是 setup.cfg 文件中:

 1[isort]
 2multi_line_output = 3
 3include_trailing_comma = True
 4force_grid_wrap = 0
 5use_parentheses = True
 6ensure_newline_before_comments = True
 7line_length = 88
 8
 9[flake8]
10max-line-length = 88
11extend-ignore = E203, W503
12
13

這樣就能使得 black、isort 和 flake8 能夠更好地協同工作;同時我們可以將其放在一個腳本文件中,方便每次運行調用,大致類似於:

1# code_fmt.sh
2
3WORKDIR=$PWD
4isort $WORKDIR \
5    && black $WORKDIR --skip-string-normalization \
6    && flake8 $WORKDIR
7
8

當然我們也可以將其集成在我們的 IDE 中,比如在 VS Code 中,當我們安裝好上述工具後,可以在設置項中關於 Python 的配置項裏找到關於上述工具的配置欄目。這裏我就以 black 爲例:

isort、flake8 的設置也是類似。

然後再次在設置中找到關於編輯器的設置,並勾選 Format on Save 的選項,這樣在我們每次保存代碼文件時,就會自動幫我們格式化:

作者:100gle,練習時長不到兩年的非正經文科生一枚,喜歡敲代碼、寫寫文章、搗鼓搗鼓各種新事物;現從事有關大數據分析與挖掘的相關工作。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/yZVNpaqCFZDD4Z8wbON6Ww