Python 的靜態型別檢查並非強制,但能有效提升程式碼品質與可維護性。本文將探討如何使用 mypy 進行靜態型別檢查,以及如何處理一些常見的挑戰,例如混合型別列表的處理。同時,我們也會介紹如何使用 reveal_type 函式來除錯 mypy 的錯誤訊息,並透過明確的型別提示來引導 mypy 正確推斷變數型別。此外,flake8 和 black 等程式碼風格檢查與格式化工具也能有效提升程式碼品質,並確保團隊成員遵循一致的程式碼風格。最後,我們將探討如何使用 pre-commit hooks 將這些檢查自動化,在每次提交前執行,以防止錯誤程式碼進入程式碼函式庫。
使用靜態型別檢查的藝術:何時使用與何時避免
在Python開發中,靜態型別檢查(static typing)是一項可選功能。有些開發者喜歡這種更嚴謹的編碼風格,但如果這種風格對你來說並不自然,那麼就不必僅僅因為它能讓工具變得更容易而轉換到這種風格。
靜態型別檢查的除錯與過度使用
在使用mypy進行靜態型別檢查時,有時需要檢視除錯資訊。Mypy沒有互動式除錯器,因此當我們遇到錯誤時,需要使用類別似printf的除錯方式,透過reveal_type函式來檢視變數的型別。
示例:使用reveal_type進行除錯
假設我們有一個測試指令碼broken.py,它使用了sensors.py中的一些程式碼,但方式不正確:
from sensors import CPULoad
sensor = CPULoad()
print(sensor.format("3.2"))
當我們執行pipenv run mypy broken.py時,會得到預期的錯誤:
broken.py:4: error: Argument 1 to "format" of "CPULoad" has incompatible type "str"; expected "float"
但如果我們更新broken.py使其變得更複雜:
from sensors import CPULoad, ACStatus
two_sensors = [CPULoad(), ACStatus()]
print(two_sensors[0].format("3.2"))
重新執行mypy後,錯誤訊息變得更基本:
broken.py:4: error: "object" has no attribute "format"
此時,mypy似乎推斷出了錯誤的型別給two_sensors列表。我們可以在定義two_sensors之後新增reveal_type(two_sensors)來檢視mypy發現了什麼。
程式碼解密:
reveal_type(two_sensors):這是一個mypy的特殊語法,用於顯示變數的推斷型別。它不是一個真實的函式,不需要在程式碼中匯入。- 新增明確型別:我們可以從
typing模組中匯入必要的名稱,並為two_sensors新增明確的型別,例如:這樣,mypy就能正確推斷出from typing import List, Any two_sensors: List[Sensor[Any]] = [CPULoad(), ACStatus()]two_sensors的型別。
何時使用靜態型別檢查與何時避免
靜態型別檢查是一種幫助開發者自身的工具,而不是一種檢測所有可能錯誤的方法。你需要在編碼過程中權衡個別型別檢查的好處與增加程式碼複雜性的弊端。
示例:混合型別的挑戰
當我們處理混合型別的感測器列表時,靜態型別檢查可能會變得困難。例如:
two_sensors: List[Union[Sensor[float], Sensor[Optional[bool]]]] = [CPULoad(), ACStatus()]
這種情況下,mypy仍然能夠檢測到錯誤,但它無法確定正確的函式簽名。
程式碼解密:
List[Union[Sensor[float], Sensor[Optional[bool]]]]:這表示two_sensors列表中的元素可以是Sensor[float]或Sensor[Optional[bool]]型別。- 函式簽名的推斷:透過使用
reveal_type(two_sensors[0].format),我們可以看到mypy推斷出的函式簽名。
靜態型別檢查在Python開發中的應用
在軟體開發過程中,靜態型別檢查是一種重要的工具,可以幫助開發者捕捉程式碼中的錯誤,提高程式碼的品質和可維護性。Python作為一種動態型別語言,雖然具有靈活性和易用性,但也因此容易出現型別相關的錯誤。為瞭解決這個問題,Python引入了型別提示(Type Hinting)的功能,允許開發者在程式碼中新增型別資訊,以便使用靜態型別檢查工具(如mypy)進行檢查。
為何使用靜態型別檢查
是否使用靜態型別檢查取決於開發團隊的偏好和專案的需求。如果團隊成員喜歡靜態型別檢查帶來的嚴謹性和可維護性,那麼使用靜態型別檢查是個好主意。對於已經投入大量時間和精力進行程式碼審查和測試的團隊來說,靜態型別檢查的益處可能會相對較小。
對外函式庫的型別提示
如果您正在撰寫一個供他人使用的函式庫,那麼至少應該為外部介面新增型別提示。這樣,使用者就可以在不將您的函式庫標記為排除在型別檢查過程之外的情況下使用型別提示。
實作型別提示
在本文中,我們將為程式碼新增型別提示。由於程式碼是由一個不反對型別提示的人撰寫的,因此沒有特別的理由避免使用它。使用型別提示有兩個好處。首先,如果在程式碼範例中發現一個小錯誤,書籍很難更新。使用型別提示可以使程式碼第一次就正確。其次,如果您以前使用過它,那麼對於這個功能在您的專案中是否有用,您將更容易有好的直覺。
將型別提示與程式碼分開
除了直接在程式碼中新增型別提示外,還可以將型別提示與程式碼分開,放在.pyi檔案中。對於熟悉C程式設計的人來說,這些檔案就像.h檔案一樣。它們維護了程式碼的結構,但沒有實作。這對於大多數開發人員不使用型別提示(例如,如果它們是為外部程式碼消費者設計的)或者您的型別結構太複雜以至於使程式碼看起來混亂的情況可能是有益的。
# sensors.pyi
from typing import Any, Generic, TypeVar
T_value = TypeVar('T_value')
class Sensor(Generic[T_value]):
title: str
def value(self) -> T_value: ...
@classmethod
def format(cls: Any, value: T_value) -> str: ...
class PythonVersion(Sensor[Any]):
title: str = ...
def value(self) -> Any: ...
@classmethod
def format(cls: Any, value: Any) -> str: ...
內容解密:
T_value = TypeVar('T_value'):定義了一個泛型變數T_value,允許Sensor類別能夠處理不同型別的資料。class Sensor(Generic[T_value])::定義了一個泛型類別Sensor,它可以根據不同的T_value型別進行例項化。def value(self) -> T_value::定義了一個方法value,傳回型別為T_value,表示傳回值的型別與例項化的T_value相同。@classmethod def format(cls: Any, value: T_value) -> str::定義了一個類別方法format,用於格式化給定的值,傳回一個字串。
產生stub檔案
可以使用mypy提供的stubgen工具從標準的Python檔案中產生.pyi檔案。這些產生的檔案需要編輯,因為它們只包含typing.Any型別的宣告。
> pipenv run stubgen sensors.py
> cp out/sensors.pyi ./sensors.pyi
Linting
Linting是一個廣義的概念,涵蓋了許多不同型別的靜態程式碼分析。mypy進行的靜態分析是一種非常技術化、以電腦科學為驅動的linting。本文討論的linting則更為簡單,更容易被引入到現有的專案中。
內容解密:
- Linting是一種靜態程式碼分析,可以幫助開發者發現程式碼中的錯誤和潛在問題。
- mypy是一種專門針對Python的靜態型別檢查工具,可以幫助開發者捕捉型別相關的錯誤。
練習2-3:擴充套件型別覆寫
我們已經為感測器建立了一個基礎類別,並研究瞭如何將其應用於一個感測器。現在,請遍歷sensors.py檔案中的其他感測器,並更新它們以使用感測器基礎類別和適當的型別提示。
內容解密:
- 本練習要求讀者自行更新
sensors.py中的其他感測器類別,以繼承自Sensor基礎類別並新增適當的型別提示。 - 讀者可能需要使用
--strict命令列標誌來執行mypy,以檢視額外的警告。
使用 linter 提升 Python 程式碼品質
在開發 Python 軟體時,保持程式碼品質和一致性是非常重要的。為此,我們可以使用 linter 工具來檢查程式碼是否符合最佳實踐和特定的風格。本文將介紹兩種流行的 linter 工具:flake8 和 black。
為什麼使用 linter?
linter 工具可以幫助我們發現程式碼中的潛在問題,例如語法錯誤、風格不一致等。flake8 是一個非常流行的 linter 工具,它根據 Python Enhancement Proposal (PEP8) 風格,並提供了許多額外的檢查功能。
flake8 和 black 的區別
flake8 主要用於檢查程式碼的風格和最佳實踐,而 black 則是一個程式碼格式化工具,可以自動將程式碼格式化為一致的風格。black 的優點在於,它可以減少因風格不一致而產生的爭議,並且可以節省時間。
安裝 flake8 和 black
要安裝 flake8 和 black,可以使用 pipenv 工具:
pipenv install --dev flake8 black
使用 flake8 和 black
安裝完成後,可以使用以下命令執行 flake8 和 black:
pipenv run flake8 sensors.py
pipenv run black sensors.py tests
flake8 將檢查程式碼中的風格和最佳實踐問題,而 black 將自動格式化程式碼。
組態 flake8
要組態 flake8,可以在 setup.cfg 檔案中新增 [flake8] 段落。例如,要設定最大行長度,可以新增以下內容:
[flake8]
max-line-length = 88
這將使 flake8 使用與 black 相同的最大行長度。
修復程式碼中的問題
執行 flake8 後,您可能會看到一些錯誤和警告。這些錯誤和警告可能是由於風格不一致、未使用的變數等引起的。可以根據 flake8 的輸出來修復這些問題。
修復範例
例如,flake8 可能會報告以下錯誤:
tests\test_acstatus.py:2:1: F401 'socket' imported but unused
tests\test_acstatus.py:41:26: E712 comparison to True should be 'if cond is True:' or 'if cond:'
這些錯誤可以透過刪除未使用的 import 陳述式和使用正確的比較運算元來修復。
最佳實踐
在使用 linter 工具時,以下是一些最佳實踐:
- 在開發環境中安裝 linter 工具,而不是在生產環境中。
- 組態 linter 工具以符合您的專案需求。
- 定期執行 linter 工具以發現潛在問題。
- 使用程式碼格式化工具(如 black)來保持程式碼的一致性。
使用 black 的注意事項
在使用 black 時,需要注意以下事項:
- black 會自動格式化程式碼,因此需要小心使用,以避免不必要的更改。
- 如果您正在貢獻到一個不使用 black 的專案,請確保只提交您打算更改的內容。
- 可以使用
git add --patch命令來選擇要提交的更改。
flake8 和 black 的整合
flake8 和 black 可以一起使用,以提供更全面的程式碼檢查和格式化功能。透過組態 flake8 以符合 black 的風格,您可以確保程式碼的一致性和品質。
# sensors.py
import os
def read_sensor_data():
# ... (省略實作細節)
return data
# 使用 black 格式化後的程式碼
def read_sensor_data():
data = []
# ... (省略實作細節)
return data
內容解密:
- 使用black格式化程式碼:black 自動將程式碼格式化為一致的風格,無需手動調整格式。
- flake8的檢查功能:flake8 檢查程式碼中的風格和最佳實踐問題,幫助開發者發現潛在錯誤。
- 組態flake8:透過在
setup.cfg中新增[flake8]段落,可以自定義 flake8 的檢查規則,例如設定最大行長度。 - 整合使用flake8和black:結合兩者的優點,提供全面的程式碼檢查和格式化功能,確保程式碼品質和一致性。
自動化程式碼檢查與提交驗證
在軟體開發過程中,保持程式碼的品質與一致性是非常重要的。為了達成這個目標,使用自動化的工具來檢查程式碼錯誤、格式以及型別檢查等變得越來越普遍。本文將介紹如何使用 pre-commit 這個工具來自動化這些檢查,並確保每次提交的程式碼都符合專案的標準。
為什麼需要自動化檢查?
手動執行各種檢查工具(如 black、mypy、flake8 等)不僅耗時,還容易被遺忘。一旦錯誤的程式碼被提交,要修復就會變得更加困難。因此,將這些檢查自動化,並在每次提交前執行,是最佳的做法。
使用 pre-commit 自動化檢查
pre-commit 是一個用 Python 編寫的工具,可以管理 Git 的 hooks,以確定是否允許某次提交。它可以輕易地與現有的開發工具鏈整合。
安裝 pre-commit
首先,需要安裝 pre-commit。可以使用 pipenv 來安裝:
pipenv install --dev pre-commit
組態 pre-commit
接下來,需要組態 .pre-commit-config.yaml 檔案,告訴 pre-commit 要執行哪些檢查。雖然 pre-commit 支援使用社群編寫的組態,但直接在本地組態通常更為方便。
repos:
- repo: local
hooks:
- id: black
name: black
entry: pipenv run black
args: [--quiet]
language: system
types: [python]
- id: mypy
name: mypy
entry: pipenv run mypy
args: ["--follow-imports=skip"]
language: system
types: [python]
- id: flake8
name: flake8
entry: pipenv run flake8
language: system
types: [python]
啟用 pre-commit
組態完成後,需要在本地 Git 倉函式庫中啟用 pre-commit:
pipenv run pre-commit install
從此以後,每次提交都會觸發設定的檢查。
略過檢查
如果需要,可以使用 --no-verify 引數來略過檢查,或者設定 SKIP 環境變數來指定略過某些檢查。
SKIP="mypy" git commit
使用 git add --patch 的優勢
對於喜歡使用 git add --patch 來互動式地暫存修改內容的開發者來說,pre-commit 能夠很好地處理未暫存的修改,將其儲存在獨立的儲存區中,以確保驗證和格式化工具只作用於已暫存的程式碼。
在 Pull Requests 中執行檢查
現代的版本控制系統前端,如 GitHub 和 GitLab,支援持續整合(CI)hooks,可以在提交、分支和 Pull Requests 上執行驗證,並在使用者介面中標註結果。選擇合適的 CI 工具取決於專案的需求和團隊的偏好。
連續整合的好處
持續整合工具可以為專案維護者和外部貢獻者提供即時的反饋,幫助發現潛在的問題。無論是開源專案還是內部開發團隊,這些工具都能提升程式碼品質和團隊效率。
軟體開發中的測試、檢查與靜態分析
在軟體開發的過程中,確保程式碼的品質與可靠性是至關重要的。為了達到這個目標,開發者通常會採用多種技術來驗證程式碼的正確性與穩定性。在本章中,我們將探討三種主要的技術:測試、靜態型別檢查和靜態分析(Linting),並分析它們在軟體開發過程中的重要性和應用場景。
測試:確保程式碼的正確性
測試是軟體開發中不可或缺的一部分。透過撰寫測試案例,開發者可以驗證程式碼是否按照預期工作。測試可以分為多種型別,包括單元測試、整合測試和端對端測試等。單元測試關注的是個別函式或類別的行為,而整合測試則檢查多個元件之間的互動是否正確。
測試的益處
- 提高程式碼品質:透過測試,開發者可以及早發現並修復錯誤,從而提高程式碼的品質。
- 減少除錯時間:當程式碼出現問題時,完善的測試可以幫助開發者快速定位問題所在。
- 促進重構:有了完善的測試,開發者可以更放心地進行程式碼重構,以改善程式碼的結構和效能。
持續整合(CI)的重要性
持續整合是一種軟體開發實踐,透過自動化的方式,在每次程式碼提交後執行測試、建置和其他檢查。這樣可以確保程式碼變更不會引入新的錯誤,並且能夠及早發現問題。持續整合對於開源專案尤其重要,因為它可以幫助維護者快速驗證貢獻者的變更。
靜態型別檢查:提升程式碼的可維護性
靜態型別檢查是一種在不執行程式碼的情況下,檢查程式碼中的型別錯誤的技術。Python 作為一種動態語言,雖然靈活性高,但也容易出現型別相關的錯誤。透過使用像是 mypy 這樣的工具,可以在開發過程中捕捉到這些錯誤。
靜態型別檢查的優點
- 改善程式碼可讀性:型別提示(Type Hints)可以讓其他開發者更容易理解函式或類別的預期輸入和輸出。
- 減少執行時錯誤:靜態型別檢查可以在程式碼執行前發現潛在的型別錯誤,從而減少執行時的意外。
靜態分析(Linting):最佳化程式碼風格與品質
靜態分析工具(如 flake8)用於檢查程式碼中的風格問題、潛在錯誤和其他不良實踐。這些工具可以幫助團隊保持一致的程式碼風格,並且發現一些常見的錯誤。
靜態分析的優勢
- 統一程式碼風格:透過強制執行統一的編碼規範,可以提高團隊協作效率。
- 發現潛在問題:靜態分析工具可以檢測出一些可能被忽略的錯誤或不良實踐。
在下一章中,我們將探討如何將軟體封裝成可安裝的形式,並提供一個外掛架構,以便於擴充套件現有的感測器集合。這將進一步展示如何透過軟體工程的最佳實踐來構建可擴充套件和可維護的系統。
參考資源
- typeshed 函式庫包含了標準函式庫和許多第三方函式庫的型別提示,是學習複雜型別提示的好資源。其 Git 倉函式庫地址為 https://github.com/python/typeshed。
- pre-commit 的檔案提供了有關高階功能和各種工具的預寫掛鉤的大量資訊。詳情請參閱 https://pre-commit.com/。
- PEP561 定義瞭如何分發型別提示,尤其是作為僅提供現有包提示的包。有關更多資訊,請存取 www.python.org/dev/peps/pep-0561/#stub-only-packages。
- flake8 的錯誤程式碼列表可在 https://flake8.pycqa.org/en/latest/user/error-codes.html 找到,這些程式碼除了 pycodestyle 列表(https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes)之外還會被使用。