Python 程式碼品質的提升仰賴自動化工具的輔助,諸如程式碼檢查與格式化工具。程式碼檢查工具能有效識別程式碼中潛在的錯誤和不規範之處,例如 mypy 和 pytype 可用於靜態型別檢查,pylint 則能檢查程式碼風格和潛在的 bug。格式化工具如 black 和 yapf 則能自動調整程式碼格式,確保團隊遵循一致的程式碼風格,減少程式碼審查的負擔,並提升程式碼可讀性。這些工具的整合運用能有效提升程式碼品質,減少錯誤,並提升團隊協作效率。

自動化程式碼檢查:提升程式碼品質的關鍵

在前面的章節中,我們已經瞭解程式碼格式、一致的佈局和適當的縮排是程式碼函式庫中不可或缺的特徵。然而,這些只是基本要求。作為具有高度品質意識的工程師,我們不僅要關注這些基礎層面,還需要探討程式碼中的模式,以真正理解其含義並提供有價值的見解。

自動化檢查的重要性

所有這些檢查都應該自動化。它們應該是測試或檢查清單的一部分,並且應該整合到持續整合(CI)構建流程中。如果這些檢查未透過,則應使構建流程失敗。這是確保程式碼結構在任何時候都保持一致的唯一方法。同時,它也為團隊提供了一個客觀的參考標準。

以往,工程師或團隊長官需要在程式碼評審中反覆指出相同的問題,例如不符合PEP-8規範。現在,透過自動化工具,這些問題可以被客觀地檢測出來,並使構建流程失敗。

使用工具進行自動化檢查

本章節介紹的工具將為您提供自動檢查程式碼的思路。這些工具應該強制執行某些標準。通常,它們是可組態的,每個儲存函式庫都可以有自己的組態。

使用工具的目的是為了實作可重複和自動化的檢查。這意味著每個工程師都應該能夠在本地開發環境中執行這些工具,並獲得與團隊其他成員相同的結果。同時,這些工具應該被組態為CI構建流程的一部分。

型別一致性檢查

型別一致性是我們希望自動檢查的主要事項之一。Python是動態型別語言,但我們仍然可以新增型別註解,以提示讀者(和工具)預期在程式碼的不同部分會出現什麼。儘管註解是可選的,但正如我們所見,新增它們是一個好主意,不僅因為它使程式碼更具可讀性,還因為我們可以將註解與一些工具結合使用,以自動檢查一些常見的錯誤,這些錯誤很可能是錯誤。

使用mypy進行型別檢查

自從Python引入型別提示以來,已經開發了許多用於檢查型別一致性的工具。在本章節中,我們將介紹兩個工具:mypy(https://github.com/python/mypy)和pytype(https://github.com/google/pytype)。有多個工具可供選擇,您甚至可能選擇使用不同的工具,但總的來說,無論使用哪種特定的工具,同樣的原理都適用:重要的是要有自動驗證變更的方法,並將這些驗證作為CI構建流程的一部分。

mypy是Python中主要的靜態型別檢查工具。其理念是,一旦安裝它,它將分析專案中的所有檔案,檢查型別使用的一致性。這非常有用,因為大多數時候,它將及早發現實際的錯誤,但有時它可能會產生假陽性。

您可以使用pip安裝它,建議將其作為專案的依賴項包含在setup檔案中:

$ pip install mypy

一旦它安裝在虛擬環境中,您只需執行上述命令,它就會報告所有型別檢查的發現結果。盡量遵守其報告,因為大多數時候,它提供的見解有助於避免錯誤,否則這些錯誤可能會進入生產環境。然而,該工具並非完美,因此如果您認為它報告的是假陽性,可以使用以下註解忽略該行:

type_to_ignore = "something"  # type: ignore

程式碼範例與型別檢查

在下面的範例中,有一個函式旨在接收一個引數進行迭代。最初,任何可迭代物件都可以工作,因此我們希望利用Python的動態型別特性,允許函式傳遞列表、元組、字典的鍵、集合或幾乎任何支援for迴圈的物件:

def broadcast_notification(
    message: str,
    relevant_user_emails: Iterable[str]
):
    for email in relevant_user_emails:
        logger.info("Sending %r to %r", message, email)

內容解密:

  1. broadcast_notification 函式接受兩個引數:messagerelevant_user_emails
  2. relevant_user_emails 的型別被註解為 Iterable[str],表示它應該是一個可迭代的字串集合。
  3. 函式遍歷 relevant_user_emails,並對每個電子郵件地址記錄一條訊息,表示正在傳送 message 給該地址。
  4. 如果傳遞錯誤的引數型別,例如直接傳遞一個字串給 relevant_user_emails,mypy 將報告型別不相容的錯誤。

問題在於,如果程式碼的其他部分錯誤地傳遞了這些引數,mypy不會報告錯誤:

broadcast_notification("welcome", "user1@domain.com")

當然,這不是一個有效的例項,因為它將遍歷字串中的每個字元,並嘗試將其用作電子郵件地址。

如果我們對該引數設定的型別更加嚴格(例如,只接受字串列表或元組),那麼執行mypy確實可以識別這種錯誤場景:

$ mypy <file-name>
error: Argument 2 to "broadcast_notification" has incompatible type "str"; expected "Union[List[str], Tuple[str]]"

同樣,pytype也是可組態的,並且以類別似的方式工作,因此您可以根據專案的特定上下文調整這兩個工具。我們可以看到這個工具報告的錯誤與前一種情況非常相似。

自動化程式碼檢查與格式化工具在軟體開發中的重要性

在軟體開發過程中,保持程式碼的品質和一致性是至關重要的。為了達成這一目標,開發團隊通常會採用各種工具來進行程式碼檢查和格式化。本文將介紹幾種常見的Python程式碼檢查和格式化工具,並探討它們在軟體開發中的作用。

程式碼檢查工具

程式碼檢查工具的主要功能是檢查程式碼是否符合特定的規範或標準,例如PEP-8。這些工具可以幫助開發者發現程式碼中的錯誤、不規範的寫法以及潛在的問題。

pytype:靜態型別檢查工具

pytype是一個靜態型別檢查工具,可以檢查Python程式碼中的型別錯誤。它不僅檢查函式定義與呼叫之間的型別匹配,還會嘗試推斷執行時期的程式碼正確性,並報告潛在的執行時期錯誤。

pylint:程式碼檢查工具

pylint是一個非常完整的程式碼檢查工具,可以檢查Python專案中的多種問題,包括但不限於PEP-8規範、函式引數過多、缺少檔案字串等。pylint具有高度的可組態性,開發者可以根據專案的需求自訂檢查規則。

$ pip install pylint

使用pylint非常簡單,只需在終端機中執行pylint命令即可對程式碼進行檢查。開發者可以透過建立pylintrc設定檔來客製化檢查規則。

[DESIGN]
disable=missing-function-docstring

Coala:多語言程式碼檢查工具

Coala是一個支援多種程式語言的程式碼檢查工具。它可以根據設定檔對程式碼進行檢查,並提供自動修復建議。開發者可以根據專案的需求撰寫自訂的檢查模組。

自動格式化工具

自動格式化工具可以自動將程式碼格式化為符合特定規範的格式,從而減少因為程式碼風格不同而引起的爭議。

black:Python程式碼格式化工具

black是一個Python程式碼格式化工具,它以一種獨特且確定性的方式格式化程式碼,不允許任何引數(除了行長度)。這意味著black會將所有字串格式化為使用雙引號,引數的順序也會遵循相同的結構。

black -l 79 *.py

使用black可以確保程式碼的一致性,從而使程式碼變更更加清晰。

實施程式碼檢查和格式化的好處

  1. 提高程式碼品質:透過使用程式碼檢查工具,可以發現並修復程式碼中的錯誤和不規範之處。
  2. 減少爭議:自動格式化工具可以減少因為程式碼風格不同而引起的爭議,使團隊成員能夠專注於程式碼的邏輯。
  3. 提高可讀性:一致的程式碼風格可以提高程式碼的可讀性,使其他開發者更容易理解和維護程式碼。
內容解密:

這段內容主要闡述了在軟體開發中使用自動化程式碼檢查與格式化工具的重要性。首先介紹了幾種常見的Python程式碼檢查工具,如pytypepylintCoala,並說明瞭它們的功能和使用方法。接著,介紹了自動格式化工具black,並探討了實施程式碼檢查和格式化的好處,包括提高程式碼品質、減少爭議和提高可讀性。最後,得出結論,使用這些工具可以提高軟體開發的效率和品質。

自動化程式碼檢查與格式化的重要性

在軟體開發過程中,保持程式碼的整潔與一致性是至關重要的。程式碼不僅需要正確地實作功能,還需要具備良好的可讀性和可維護性。為了達成這些目標,開發者通常會遵循特定的編碼風格和。然而,手動檢查和維護程式碼的格式不僅耗時,還容易出錯。因此,自動化工具的使用變得尤為重要。

自動格式化工具:Black 與 YAPF

Python 社群中有多種工具可以用於自動格式化程式碼,其中最為知名的是 blackyapf

Black:統一程式碼風格的利器

black 是一個極為流格的 Python 程式碼格式化工具,它遵循 PEP 8 的規範,並在此基礎上進行了擴充套件。black 的最大特點是其格式化結果的一致性和徹底性。當你將 black 應用於程式碼時,它會對程式碼進行全面的格式化處理,使其符合預定的編碼標準。

使用 black 的好處在於,它可以減少團隊成員之間因程式碼風格不同而產生的爭議。只需在專案中組態 black,即可確保所有程式碼都遵循統一的格式。

# 使用 black 格式化程式碼的範例
def greet(name: str) -> str:
    return "received {0}".format(name.title())

內容解密:

  • 此範例展示了一個簡單的函式 greet,它接受一個名字並傳回一個問候訊息。
  • black 會自動調整程式碼的格式,使其符合 PEP 8 規範。
  • 例如,black 可能會自動在運算元前後新增適當的空格,以提高可讀性。

YAPF:高度可定製的格式化工具

black 不同,yapf 提供了更高的可定製性。它允許開發者根據自己的需求調整格式化規則,甚至可以只對程式碼的特定部分進行格式化。這使得 yapf 成為處理舊專案或需要特殊格式要求的專案的理想選擇。

# 使用 yapf 格式化程式碼的範例
def complex_function(
    parameter1: int, parameter2: str, parameter3: dict
) -> tuple:
    # 複雜的邏輯處理
    result = (parameter1, parameter2, parameter3)
    return result

內容解密:

  • 此範例展示了一個名為 complex_function 的函式,它接受多個引數並傳回一個 tuple。
  • yapf 可以根據組態,對函式的引數列表和函式體進行格式化。
  • 特別是對於具有多個引數的函式,yapf 可以使程式碼更易讀。

自動檢查與 CI/CD 流程整合

除了使用自動格式化工具之外,將這些工具整合到持續整合/持續佈署(CI/CD)流程中,可以進一步提高程式碼的品質。

使用 Makefile 管理專案任務

在 Unix-like 的開發環境中,Makefile 是一種常見的管理專案任務的方式。透過定義不同的目標(target),可以執行各種檢查和測試。

.PHONY: typehint test lint checklist black clean

typehint:
    mypy --ignore-missing-imports src/

test:
    pytest tests/

lint:
    pylint src/

checklist: lint typehint test

black:
    black -l 79 *.py

clean:
    find . -type f -name "*.pyc" | xargs rm -fr
    find . -type d -name __pycache__ | xargs rm -fr

內容解密:

  • 此 Makefile 定義了多個目標,用於執行不同的檢查和清理任務。
  • typehint 目標使用 mypy 檢查型別提示。
  • test 目標執行測試。
  • lint 目標使用 pylint 檢查程式碼品質。
  • checklist 目標綜合執行上述檢查。
  • black 目標使用 black 格式化程式碼。
  • clean 目標清理產生的臨時檔案。

Pythonic 程式碼

本章節將探討 Python 中表達思想的方式及其獨特性。如果你熟悉程式設計中某些任務的標準實作方式(例如取得列表的最後一個元素、迭代和搜尋),或者你來自其他程式語言(例如 C、C++ 和 Java),那麼你將發現 Python 通常提供自己的機制來完成大多數常見任務。

在程式設計中,idiom(慣用法)是一種特定的編寫程式碼的方式,用於執行特定的任務。它是一種常見的重複模式,每次都遵循相同的結構。有些人甚至可能稱它們為模式,但要注意的是,它們不是設計模式(我們稍後會探討)。兩者的主要區別在於,設計模式是高層次的思想,相對獨立於語言,但不能直接轉化為程式碼。另一方面,慣用法實際上是編寫程式碼的方式。當我們想要執行特定任務時,應該按照慣用法來寫。

由於慣用法是與語言相關的程式碼,因此每種語言都有自己的慣用法,這意味著在特定語言中做事的方式(例如,在 C 或 C++ 中如何開啟和寫入檔案)。當程式碼遵循這些慣用法時,它被稱為 idiomatic(慣用語),在 Python 中通常被稱為 Pythonic。

遵循這些建議並編寫 Pythonic 程式碼有多個原因。首先,如我們將要分析和看到的那樣,以慣用方式編寫程式碼通常表現更好。它也更緊湊、更容易理解。這些是我們始終希望在程式碼中擁有的特徵,以便它有效地工作。

其次,如前一章所介紹的那樣,整個開發團隊能夠習慣相同的程式碼模式和結構非常重要,因為這將幫助他們專注於問題的真正本質,並避免犯錯。

本章的目標如下:

  • 瞭解索引和切片,並正確實作可索引的物件
  • 實作序列和其他可迭代物件
  • 瞭解上下文管理器的良好使用案例,以及如何編寫有效的上下文管理器
  • 透過魔術方法實作更具慣用性的程式碼
  • 避免 Python 中常見的錯誤,導致不想要的副作用

我們首先在下一節中探討列表中的第一項(索引和切片)。

索引和切片

在 Python 中,與其他語言一樣,一些資料結構或型別支援透過索引存取其元素。與大多數程式語言相同,第一個元素位於索引號 0。然而,與那些語言不同的是,當我們想要以不同的順序存取元素時,Python 提供了額外的功能。

例如,在 C 中如何存取陣列的最後一個元素?第一次使用 Python 時,我會這樣做。以與 C 相同的方式思考,我會取得陣列長度減一位置的元素。在 Python 中,這樣做也是有效的,但我們也可以使用負索引號,它將從最後一個元素開始計數,如下面的命令所示:

>>> my_numbers = (4, 5, 3, 9)
>>> my_numbers[-1]
9
>>> my_numbers[-3]
5

這是首選(Pythonic)的做事方式的例子。

除了取得單個元素外,我們還可以使用切片取得多個元素,如下面的命令所示:

>>> my_numbers = (1, 1, 2, 3, 5, 8, 13, 21)
>>> my_numbers[2:5]
(2, 3, 5)

在這種情況下,方括號中的語法表示我們取得元組中的所有元素,從第一個數字的索引(包含)開始,直到第二個數字的索引(不包含)。切片在 Python 中的工作方式是排除所選區間的末尾。

您可以排除任一區間,即開始或停止,在這種情況下,它將分別從序列的開始或結束開始執行,如下面的命令所示:

>>> my_numbers[:3]
(1, 1, 2)
>>> my_numbers[3:]
(3, 5, 8, 13, 21)
>>> my_numbers[::] # 也等同於 my_numbers[:],傳回一個副本
(1, 1, 2, 3, 5, 8, 13, 21)
>>> my_numbers[1:7:2]
(1, 3, 8)

在第一個例子中,它將取得直到索引位置 3 的所有內容。在第二個例子中,它將取得從位置 3(包含)開始直到結束的所有數字。在倒數第二個例子中,兩個端點都被排除,它實際上建立了原始元組的副本。

最後一個例子包含第三個引數,即步長。這表示在迭代區間時要跳過多少個元素。在這種情況下,它意味著取得位置一和七之間的元素,每次跳過兩個元素。

在所有這些情況下,當我們向序列傳遞區間時,實際上發生的是我們正在傳遞一個切片。注意,切片是 Python 中的內建物件,您可以自己構建並直接傳遞:

>>> interval = slice(1, 7, 2)
>>> my_numbers[interval]
(1, 3, 8)
>>> interval = slice(None, 3)
>>> my_numbers[interval] == my_numbers[:3]
True

注意,當缺少其中一個元素(開始、停止或步長)時,它被視為 None。

您應該始終優先使用此內建語法進行切片,而不是手動嘗試在 for 迴圈中迭代元組、字串或列表,並手動排除元素。

建立您自己的序列

我們剛剛討論的功能之所以有效,是因為有一個名為 __getitem__ 的魔術方法(魔術方法是 Python 中用於保留特殊行為的雙下劃線包圍的方法)。當像 myobject[key] 這樣的呼叫發生時,會呼叫此方法,並將鍵(方括號內的值)作為引數傳遞。特別是,序列是一個實作了 __getitem____len__ 的物件,因此它可以被迭代。列表、元組和字串是標準函式庫中序列物件的示例。

在本文中,我們更關心的是透過鍵從物件中取得特定元素,而不是構建序列或可迭代物件,這是一個話題。#### 建立您自己的序列

要建立自己的序列,需要實作 __getitem____len__ 這兩個魔術方法。這使得物件能夠支援索引和切片操作,並且可以被迭代。

class MySequence:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, index):
        return self.data[index]

    def __len__(self):
        return len(self.data)

# 使用範例
my_seq = MySequence([1, 2, 3, 4, 5])
print(my_seq[0])   # 輸出:1
print(my_seq[1:3]) # 輸出:[2, 3]
print(len(my_seq)) # 輸出:5

索引和切片的內容解密

在上述範例中,我們定義了一個名為 MySequence 的類別,並實作了 __getitem____len__ 方法。這使得 MySequence 的例項能夠像列表一樣支援索引和切片操作。

  • __getitem__ 方法允許物件支援索引和切片。當我們使用 my_seq[index] 這樣的語法時,Python 會自動呼叫 my_seq.__getitem__(index)
  • __len__ 方法傳回物件的長度,這使得物件能夠支援 len() 函式。

透過實作這兩個方法,我們可以建立出支援豐富操作的序列物件,使其能夠更好地融入 Python 的生態系統中。