Following system colour scheme - Python 增強提案 Selected dark colour scheme - Python 增強提案 Selected light colour scheme - Python 增強提案

Python 增強提案 (Python Enhancement Proposals)

PEP 8 – Python 程式碼風格指南

作者:
Guido van Rossum <guido at python.org>, Barry Warsaw <barry at python.org>, Alyssa Coghlan <ncoghlan at gmail.com>
狀態:
作用中
類型:
流程
建立日期:
2001年7月5日
公告歷史:
2001年7月5日,2013年8月1日

目錄

簡介

本文件提供了 Python 主發行版中標準函式庫的編碼慣例。請參閱隨附的資訊性 PEP,該 PEP 描述了 Python C 實作中的 C 程式碼風格指南

本文件與 PEP 257(文件字串慣例)改編自 Guido 最初的 Python 風格指南論文,並加入了一些 Barry 風格指南中的補充內容 [2]

隨著語言本身的演變,新的慣例被確立,舊的慣例被淘汰,本風格指南也會隨之發展。

許多專案都有自己的編碼風格指南。若發生任何衝突,對於該專案而言,專案特定的指南優先。

愚蠢的執著是小人物的妖怪

Guido 的核心觀點之一是:程式碼被閱讀的次數遠多於撰寫的次數。此處提供的指南旨在提高程式碼的可讀性,並使其在廣泛的 Python 程式碼中保持一致。如 PEP 20 所述:「可讀性很重要」。

風格指南的核心在於一致性。與本風格指南保持一致固然重要,但在專案內部保持一致更重要,而在單一模組或函式內部保持一致則最為重要。

然而,要懂得何時不必保持一致——有時風格指南的建議並不適用。若有疑慮,請運用您的最佳判斷。參考其他範例,決定哪種方式看起來最好。不要猶豫,隨時提問!

特別注意:不要為了遵守本 PEP 而破壞向後相容性!

忽略特定指南的其他一些好理由:

  1. 當應用該指南會使程式碼可讀性降低時,即使是對習慣閱讀遵循此 PEP 程式碼的人來說也是如此。
  2. 為了與周圍同樣破壞該規則的程式碼保持一致(可能是出於歷史原因)——儘管這也是清理他人遺留問題的機會(以真正的 XP 風格)。
  3. 因為該程式碼在指南引入之前就已存在,且沒有其他理由修改該程式碼。
  4. 當程式碼需要與不支援風格指南建議功能的舊版 Python 保持相容時。

程式碼佈局

縮排

每個縮排層級使用 4 個空白鍵。

續行應該透過以下方式對齊包裹的元素:垂直對齊(使用 Python 在括號、方括號和大括號內的隱式換行),或者使用懸掛縮排 (hanging indent) [1]。當使用懸掛縮排時,應考慮以下幾點:第一行不應包含參數,並且應使用額外的縮排來清楚地標識為續行。

# Correct:

# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# Add 4 spaces (an extra level of indentation) to distinguish arguments from the rest.
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

# Hanging indents should add a level.
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)
# Wrong:

# Arguments on first line forbidden when not using vertical alignment.
foo = long_function_name(var_one, var_two,
    var_three, var_four)

# Further indentation required as indentation is not distinguishable.
def long_function_name(
    var_one, var_two, var_three,
    var_four):
    print(var_one)

對於續行,4 個空白鍵的規則是選擇性的。

選擇性

# Hanging indents *may* be indented to other than 4 spaces.
foo = long_function_name(
  var_one, var_two,
  var_three, var_four)

if 語句的條件部分太長,需要跨多行書寫時,值得注意的是:兩個字元的關鍵字(即 if)加上一個空格,再加上一個左括號,會自然地為後續的多行條件行產生 4 個空白鍵的縮排。這可能會與 if 語句內部的巢狀程式碼區塊產生視覺衝突,因為後者也會自然地縮排 4 個空白鍵。本 PEP 對於如何(或是否)進一步在視覺上區分此類條件行與 if 語句內部的巢狀程式碼區塊,不採取明確立場。在此情況下,可接受的選項包括但不限於:

# No extra indentation.
if (this_is_one_thing and
    that_is_another_thing):
    do_something()

# Add a comment, which will provide some distinction in editors
# supporting syntax highlighting.
if (this_is_one_thing and
    that_is_another_thing):
    # Since both conditions are true, we can frobnicate.
    do_something()

# Add some extra indentation on the conditional continuation line.
if (this_is_one_thing
        and that_is_another_thing):
    do_something()

(也請參閱下方關於在二元運算子之前還是之後換行的討論。)

多行結構的結尾大括號/方括號/括號可以對齊在清單最後一行的第一個非空白字元之下,如:

my_list = [
    1, 2, 3,
    4, 5, 6,
    ]
result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
    )

或者,它也可以對齊在開始多行結構的該行第一個字元之下,如:

my_list = [
    1, 2, 3,
    4, 5, 6,
]
result = some_function_that_takes_arguments(
    'a', 'b', 'c',
    'd', 'e', 'f',
)

Tab 還是空白鍵?

空白鍵是首選的縮排方式。

只有為了與已經使用 Tab 縮排的程式碼保持一致時,才應使用 Tab。

Python 禁止在縮排中混用 Tab 和空白鍵。

最大行長度

限制所有行最大為 79 個字元。

對於結構限制較少的長文本區塊(文件字串或註解),行長應限制為 72 個字元。

限制編輯器視窗所需的寬度,可以讓多個檔案並排開啟,這在程式碼審查工具將兩個版本顯示在相鄰欄位時非常有效。

大多數工具的預設自動換行會破壞程式碼的視覺結構,使理解變得困難。設定這些限制是為了避免在視窗寬度設為 80 的編輯器中換行,即使工具在換行時於最後一欄放置標記字元也是如此。某些網頁工具可能根本不提供動態換行。

有些團隊強烈偏好更長的行長。對於由能就此問題達成共識的團隊獨家或主要維護的程式碼,可以將行長限制增加到 99 個字元,前提是註解和文件字串仍維持在 72 個字元換行。

Python 標準函式庫比較保守,要求將行限制為 79 個字元(文件字串/註解為 72 個字元)。

換行長行的首選方式是使用 Python 在括號、方括號和大括號內的隱式換行。長行可以透過將運算式包裹在括號中來拆分為多行。這些方式應優先於使用反斜線進行續行。

反斜線有時仍然適用。例如,在 Python 3.10 之前,長的、多個 with 語句無法使用隱式續行,因此在該情況下反斜線是可以接受的。

with open('/path/to/some/file/you/want/to/read') as file_1, \
     open('/path/to/some/file/being/written', 'w') as file_2:
    file_2.write(file_1.read())

(關於此類多行 with 語句的縮排,請參閱先前關於 多行 if 語句 的討論。)

另一個案例是 assert 語句。

請務必適當地縮排續行。

換行應該在二元運算子之前還是之後?

幾十年來,推薦的風格是在二元運算子之後換行。但這會在兩個方面損害可讀性:運算子傾向於分散在螢幕的不同欄位,且每個運算子都與其運算元分離並移動到上一行。在這裡,眼睛必須多費力才能分辨哪些項目是相加的,哪些是相減的:

# Wrong:
# operators sit far away from their operands
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

為了解決這個可讀性問題,數學家及其出版商遵循相反的慣例。Donald Knuth 在他的《電腦與排版》(Computers and Typesetting) 系列中解釋了傳統規則:「雖然段落內的公式總是在二元運算和關係之後換行,但顯示的公式總是在二元運算之前換行」[3]

遵循數學傳統通常會產生更具可讀性的程式碼:

# Correct:
# easy to match operators with operands
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

在 Python 程式碼中,允許在二元運算子之前或之後換行,只要在局部保持慣例一致即可。對於新程式碼,建議使用 Knuth 的風格。

空白行

在頂層函式和類別定義前後各留兩行空白。

類別內的方法定義前後留一行空白。

額外的空白行可以(節制地)用於分隔相關函式群組。在一堆相關的單行程式碼(例如一組模擬實作)之間可以省略空白行。

在函式中使用空白行時應有所節制,用以標示邏輯區段。

Python 接受 control-L(即 ^L)換頁字元作為空白;許多工具將這些字元視為頁面分隔符,因此您可以使用它們來分隔檔案中相關區段的頁面。請注意,某些編輯器和網頁程式碼檢視器可能無法識別 control-L 為換頁符,並會在該處顯示其他符號。

原始碼檔案編碼

核心 Python 發行版中的程式碼應始終使用 UTF-8,且不應包含編碼宣告。

在標準函式庫中,非 UTF-8 編碼應僅用於測試目的。節制使用非 ASCII 字元,最好僅用於標示地點和人名。若將非 ASCII 字元用作資料,請避免使用如 z̯̯͡a̧͎̺l̡͓̫g̹̲o̡̼̘ 之類的雜亂 Unicode 字元及位元組順序標記 (BOM)。

Python 標準函式庫中的所有識別字都「必須」僅使用 ASCII,並且在可行的地方「應該」使用英文單字(在許多情況下,會使用非英文的縮寫和技術術語)。

鼓勵擁有全球受眾的開源專案採取類似的政策。

匯入 (Imports)

  • 匯入語句通常應分行書寫:
    # Correct:
    import os
    import sys
    
    # Wrong:
    import sys, os
    

    不過這樣寫也是可以的:

    # Correct:
    from subprocess import Popen, PIPE
    
  • 匯入語句應始終放在檔案頂部,就在任何模組註解和文件字串之後,在模組全域變數和常數之前。

    匯入語句應依以下順序分組:

    1. 標準函式庫匯入。
    2. 相關的第三方匯入。
    3. 本地應用程式/函式庫特定匯入。

    您應該在每組匯入語句之間放置一個空白行。

  • 建議使用絕對匯入,因為它們通常更具可讀性,且如果匯入系統配置不當(例如當套件內的目錄最終出現在 sys.path 中時),它們的行為往往更好(或者至少能提供更好的錯誤訊息)。
    import mypkg.sibling
    from mypkg import sibling
    from mypkg.sibling import example
    

    然而,明確的相對匯入是絕對匯入的可接受替代方案,特別是在處理複雜的套件佈局時,因為在這種情況下使用絕對匯入會顯得過於冗長。

    from . import sibling
    from .sibling import example
    

    標準函式庫程式碼應避免複雜的套件佈局,並始終使用絕對匯入。

  • 當從包含類別的模組中匯入類別時,通常可以這樣寫:
    from myclass import MyClass
    from foo.bar.yourclass import YourClass
    

    如果這種寫法導致本地名稱衝突,則應明確寫出:

    import myclass
    import foo.bar.yourclass
    

    並使用 myclass.MyClassfoo.bar.yourclass.YourClass

  • 應避免使用萬用字元匯入(from <module> import *),因為這會導致命名空間中的名稱不明確,讓讀者和許多自動化工具感到困惑。萬用字元匯入有一種可辯護的用途,即將內部介面重新發佈為公開 API 的一部分(例如,用可選加速器模組的定義覆寫介面的純 Python 實作,且無法事先知道會覆寫哪些定義)。

    以這種方式重新發佈名稱時,下方關於公開與內部介面的指南仍然適用。

模組層級的雙底線名稱 (Dunder Names)

模組層級的「雙底線名稱」(即前後各兩個底線的名稱),如 __all____author____version__ 等,應放置在模組文件字串之後,但在任何匯入語句「之前」(from __future__ 匯入除外)。Python 規定 future-imports 必須出現在模組中除文件字串之外的任何其他程式碼之前。

"""This is the example module.

This module does stuff.
"""

from __future__ import barry_as_FLUFL

__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'

import os
import sys

字串引號

在 Python 中,單引號字串和雙引號字串是一樣的。本 PEP 對此不做建議。選擇一種規則並堅持使用。然而,當字串包含單引號或雙引號字元時,請使用另一種引號以避免字串中出現反斜線。這能提高可讀性。

對於三引號字串,請務必使用雙引號字元,以與 PEP 257 中的文件字串慣例保持一致。

運算式與語句中的空白

常見厭惡點 (Pet Peeves)

避免在以下情況中使用多餘的空白:

  • 括號、方括號或大括號內部緊鄰處。
    # Correct:
    spam(ham[1], {eggs: 2})
    
    # Wrong:
    spam( ham[ 1 ], { eggs: 2 } )
    
  • 尾隨逗號與後面的右括號之間。
    # Correct:
    foo = (0,)
    
    # Wrong:
    bar = (0, )
    
  • 逗號、分號或冒號之前緊鄰處。
    # Correct:
    if x == 4: print(x, y); x, y = y, x
    
    # Wrong:
    if x == 4 : print(x , y) ; x , y = y , x
    
  • 然而,在切片 (slice) 中,冒號的作用類似於二元運算子,兩側應有等量的空白(將其視為優先級最低的運算子)。在擴充切片中,兩個冒號必須應用相同的空白間距。例外:當省略切片參數時,空格也隨之省略。
    # Correct:
    ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
    ham[lower:upper], ham[lower:upper:], ham[lower::step]
    ham[lower+offset : upper+offset]
    ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
    ham[lower + offset : upper + offset]
    
    # Wrong:
    ham[lower + offset:upper + offset]
    ham[1: 9], ham[1 :9], ham[1:9 :3]
    ham[lower : : step]
    ham[ : upper]
    
  • 函式呼叫的參數列表開頭的左括號前緊鄰處。
    # Correct:
    spam(1)
    
    # Wrong:
    spam (1)
    
  • 索引或切片開頭的左括號前緊鄰處。
    # Correct:
    dct['key'] = lst[index]
    
    # Wrong:
    dct ['key'] = lst [index]
    
  • 在指派(或其他)運算子周圍使用超過一個空格,以便與另一個對齊。
    # Correct:
    x = 1
    y = 2
    long_variable = 3
    
    # Wrong:
    x             = 1
    y             = 2
    long_variable = 3
    

其他建議

  • 避免在任何地方出現尾隨空白。因為它通常是不可見的,這可能會造成混淆:例如,反斜線後接空格和換行符,並不視為續行標記。有些編輯器不會保留它,且許多專案(如 CPython 本身)都有 rejection 尾隨空白的 commit 前鉤子 (pre-commit hooks)。
  • 始終在這些二元運算子兩側各使用一個空格:指派 (=)、增量指派 (+=, -= 等)、比較 (==, <, >, !=, <=, >=, in, not in, is, is not)、布林運算 (and, or, not)。
  • 如果使用了不同優先級的運算子,請考慮在優先級最低的運算子周圍添加空白。請自行判斷;但是,絕對不要使用超過一個空格,且二元運算子兩側的空白量務必相同。
    # Correct:
    i = i + 1
    submitted += 1
    x = x*2 - 1
    hypot2 = x*x + y*y
    c = (a+b) * (a-b)
    
    # Wrong:
    i=i+1
    submitted +=1
    x = x * 2 - 1
    hypot2 = x * x + y * y
    c = (a + b) * (a - b)
    
  • 函式註解應遵循冒號的正常規則,如果存在 -> 箭頭,箭頭兩側應始終有空格。(關於函式註解的更多資訊,請參閱下方的 函式註解。)
    # Correct:
    def munge(input: AnyStr): ...
    def munge() -> PosInt: ...
    
    # Wrong:
    def munge(input:AnyStr): ...
    def munge()->PosInt: ...
    
  • = 符號用於表示關鍵字參數,或用於表示「未註解」的函式參數的預設值時,不要在周圍使用空格。
    # Correct:
    def complex(real, imag=0.0):
        return magic(r=real, i=imag)
    
    # Wrong:
    def complex(real, imag = 0.0):
        return magic(r = real, i = imag)
    

    然而,當將參數註解與預設值結合使用時,確實要在 = 符號周圍使用空格。

    # Correct:
    def munge(sep: AnyStr = None): ...
    def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
    
    # Wrong:
    def munge(input: AnyStr=None): ...
    def munge(input: AnyStr, limit = 1000): ...
    
  • 通常不鼓勵使用複合語句(即同一行有多個語句)。
    # Correct:
    if foo == 'blah':
        do_blah_thing()
    do_one()
    do_two()
    do_three()
    

    最好不要這樣:

    # Wrong:
    if foo == 'blah': do_blah_thing()
    do_one(); do_two(); do_three()
    
  • 雖然有時將 if/for/while 語句與簡短的本體放在同一行是可以的,但千萬不要對多子句語句這樣做。也要避免摺疊此類長行!

    最好不要這樣:

    # Wrong:
    if foo == 'blah': do_blah_thing()
    for x in lst: total += x
    while t < 10: t = delay()
    

    絕對不要這樣:

    # Wrong:
    if foo == 'blah': do_blah_thing()
    else: do_non_blah_thing()
    
    try: something()
    finally: cleanup()
    
    do_one(); do_two(); do_three(long, argument,
                                 list, like, this)
    
    if foo == 'blah': one(); two(); three()
    

何時使用尾隨逗號

尾隨逗號通常是選擇性的,但在建立只有一個元素的 tuple 時是必要的。為了清晰起見,建議將後者用(技術上多餘的)括號包起來。

# Correct:
FILES = ('setup.cfg',)
# Wrong:
FILES = 'setup.cfg',

當尾隨逗號是多餘的時,在使用版本控制系統,或者預計值、參數或匯入項目列表會隨時間擴充時,它們通常很有用。模式是將每個值(等)放在自己的一行上,始終加上尾隨逗號,並在下一行加入右括號/方括號/大括號。然而,在結尾分隔符的同一行上使用尾隨逗號是沒有意義的(除了上述單元素 tuple 的情況)。

# Correct:
FILES = [
    'setup.cfg',
    'tox.ini',
    ]
initialize(FILES,
           error=True,
           )
# Wrong:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)

註解

與程式碼矛盾的註解比沒有註解更糟。當程式碼變更時,始終優先考慮保持註解更新!

註解應為完整的句子。第一個單字應大寫,除非它是以小寫字母開頭的識別字(絕對不要改變識別字的大小寫!)。

區塊註解通常由一個或多個由完整句子組成的段落組成,每個句子都以句號結尾。

在多句註解中,句子結尾的句號之後應使用一個或兩個空格,最後一個句子後除外。

確保您的註解對於使用您所用語言的其他使用者來說是清晰且易於理解的。

來自非英語系國家的 Python 程式設計師:請使用英文撰寫註解,除非您 120% 確定該程式碼永遠不會被不會說您母語的人閱讀。

區塊註解

區塊註解通常適用於其後的某些(或全部)程式碼,並與該程式碼縮排在同一層級。區塊註解的每一行都以 # 和一個空格開始(註解內部的縮排文字除外)。

區塊註解內部的段落由包含單個 # 的行分隔。

行內註解

請節制使用行內註解。

行內註解是與語句在同一行的註解。行內註解與語句之間至少應分隔兩個空格。它們應以 # 和一個空格開始。

如果行內註解只是陳述顯而易見的事實,那麼它們是不必要的,甚至是干擾。不要這樣做:

x = x + 1                 # Increment x

但有時,這樣做很有用:

x = x + 1                 # Compensate for border

文件字串 (Documentation Strings)

撰寫良好文件字串(又稱「docstrings」)的慣例已在 PEP 257 中永久記錄。

  • 為所有公開的模組、函式、類別和方法撰寫文件字串。非公開方法不需要文件字串,但您應該有一條描述該方法功能的註解。此註解應出現在 def 行之後。
  • PEP 257 描述了良好的文件字串慣例。請注意,最重要的是,結束多行文件字串的 """ 應該單獨佔據一行:
    """Return a foobang
    
    Optional plotz says to frobnicate the bizbaz first.
    """
    
  • 對於單行文件字串,請將結尾的 """ 保持在同一行。
    """Return an ex-parrot."""
    

命名慣例

Python 函式庫的命名慣例有點混亂,所以我們永遠無法使其完全一致——儘管如此,以下是目前推薦的命名標準。新模組和套件(包括第三方框架)應按照這些標準編寫,但在現有函式庫風格不同的情況下,內部一致性優先。

核心原則

作為 API 公開部分對使用者可見的名稱,應遵循反映用途而非實作的慣例。

描述性:命名風格

有很多不同的命名風格。能夠識別正在使用什麼命名風格,獨立於它們的用途,會很有幫助。

以下命名風格通常被區分出來:

  • b(單個小寫字母)
  • B(單個大寫字母)
  • lowercase (全小寫)
  • lower_case_with_underscores (底線分隔小寫)
  • UPPERCASE (全大寫)
  • UPPER_CASE_WITH_UNDERSCORES (底線分隔大寫)
  • CapitalizedWords(或 CapWords,或 CamelCase —— 因其字母的起伏外觀而得名 [4])。這有時也被稱為 StudlyCaps。

    注意:在 CapWords 中使用縮寫時,請將縮寫的所有字母大寫。因此,HTTPServerError 比 HttpServerError 更好。

  • mixedCase(與 CapitalizedWords 的區別在於首字母小寫!)
  • Capitalized_Words_With_Underscores(很醜!)

還有一種使用短唯一字首將相關名稱組合在一起的風格。這在 Python 中使用不多,但為了完整性在此提及。例如,os.stat() 函式回傳一個 tuple,其項目傳統上具有如 st_mode, st_size, st_mtime 等名稱。(這樣做是為了強調與 POSIX 系統呼叫結構欄位的對應關係,這對熟悉該結構的程式設計師很有幫助。)

X11 函式庫對其所有公開函式使用字首 X。在 Python 中,這種風格通常被認為是不必要的,因為屬性和方法名稱已有物件做為字首,而函式名稱已有模組名稱做為字首。

此外,以下使用前導或尾隨底線的特殊形式是公認的(這些通常可以與任何大小寫慣例結合):

  • _single_leading_underscore:弱「內部使用」指標。例如,from M import * 不會匯入名稱以底線開頭的物件。
  • single_trailing_underscore_:按慣例用於避免與 Python 關鍵字衝突,例如:
    tkinter.Toplevel(master, class_='ClassName')
    
  • __double_leading_underscore:命名類別屬性時,觸發名稱改寫 (name mangling)(在類別 FooBar 內部,__boo 變為 _FooBar__boo;見下文)。
  • __double_leading_and_trailing_underscore__:「魔法」物件或屬性,存在於使用者控制的命名空間中。例如 __init__, __import____file__。切勿發明此類名稱;僅按文件說明使用它們。

規範性:命名慣例

應避免使用的名稱

切勿使用字元 'l'(小寫字母 el)、'O'(大寫字母 oh)或 'I'(大寫字母 eye)作為單字元變數名稱。

在某些字型中,這些字元與數字 1 和 0 無法區分。當想使用 'l' 時,請改用 'L'。

ASCII 相容性

標準函式庫中使用的識別字必須是 ASCII 相容的,如 PEP 3131政策章節 所述。

套件與模組名稱

模組應具有簡短、全小寫的名稱。如果模組名稱包含多個單字,可以使用底線以提高可讀性。Python 套件也應具有簡短、全小寫的名稱,儘管不鼓勵在套件名稱中使用底線。

當以 C 或 C++ 編寫的擴充模組具有提供更高級別(例如更物件導向)介面的 Python 模組時,C/C++ 模組具有前導底線(例如 _socket)。

類別名稱

類別名稱通常應使用 CapWords 慣例。

在介面已記錄並主要用作可呼叫物件的情況下,可以使用函式命名慣例。

請注意,內建名稱有單獨的慣例:大多數內建名稱是單字(或兩個連在一起的單字),CapWords 慣例僅用於異常名稱和內建常數。

型別變數名稱

PEP 484 中引入的型別變數名稱通常應使用 CapWords,並偏好簡短名稱:T, AnyStr, Num。建議在宣告共變 (covariant) 或反變 (contravariant) 行為的變數後加上後綴 _co_contra

from typing import TypeVar

VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)

異常名稱

因為異常應該是類別,所以適用類別命名慣例。但是,您應該在異常名稱上使用「Error」後綴(如果該異常確實是一個錯誤)。

全域變數名稱

(希望這些變數僅在單一模組內使用。)慣例與函式大致相同。

設計為透過 from M import * 使用的模組應使用 __all__ 機制來防止匯出全域變數,或者使用在全域變數前加底線的舊慣例(您可能想這樣做以表明這些全域變數是「模組非公開」的)。

函式與變數名稱

函式名稱應為小寫,必要時使用底線分隔單字以提高可讀性。

變數名稱遵循與函式名稱相同的慣例。

只有在已經是主流風格的上下文中才允許使用 mixedCase(例如 threading.py),以保留向後相容性。

函式與方法參數

始終使用 self 作為實例方法的第一個參數。

始終使用 cls 作為類別方法的第一個參數。

如果函式參數的名稱與保留關鍵字衝突,通常最好附加一個尾隨底線,而不是使用縮寫或拼寫錯誤。因此 class_clss 更好。(或者更好的是透過使用同義詞來避免這種衝突。)

方法名稱與實例變數

使用函式命名規則:小寫,必要時使用底線分隔單字以提高可讀性。

僅對非公開方法和實例變數使用一個前導底線。

為了避免與子類別的名稱衝突,請使用兩個前導底線來觸發 Python 的名稱改寫規則。

Python 會用類別名稱來改寫這些名稱:如果類別 Foo 有一個名為 __a 的屬性,它不能透過 Foo.__a 存取。(堅持的使用者仍可透過呼叫 Foo._Foo__a 來存取。)通常,雙前導底線應僅用於避免與設計為可繼承類別中的屬性發生名稱衝突。

注意:關於 __名稱的使用存在一些爭議(見下文)。

常數

常數通常在模組層級定義,並以全大寫字母書寫,單字之間以底線分隔。範例包括 MAX_OVERFLOWTOTAL

繼承設計

始終決定類別的方法和實例變數(統稱為「屬性」)應該是公開的還是非公開的。如果有疑慮,請選擇非公開;以後將其變為公開比將公開屬性變為非公開更容易。

公開屬性是指您期望類別的無關客戶端使用的屬性,且您承諾避免不相容的向後變更。非公開屬性是指不打算供第三方使用的屬性;您不保證非公開屬性不會變更甚至被移除。

我們在這裡不使用「私有」一詞,因為在 Python 中沒有什麼屬性是真正私有的(除非付出通常不必要的努力)。

另一類屬性是「子類別 API」的一部分(在其他語言中通常稱為「受保護的」)。有些類別被設計為可以被繼承,以擴充或修改類別行為的某些方面。設計此類類別時,請務必明確決定哪些屬性是公開的,哪些是子類別 API 的一部分,以及哪些真正僅供您的基底類別使用。

考慮到這一點,以下是 Python 風格的指南:

  • 公開屬性不應有前導底線。
  • 如果您的公開屬性名稱與保留關鍵字衝突,請在屬性名稱後附加一個尾隨底線。這優於縮寫或拼寫錯誤。(然而,儘管有此規則,對於任何已知為類別的變數或參數,特別是類別方法的第一個參數,'cls' 是首選的拼寫。)

    注意 1:請參閱上方關於類別方法參數名稱的建議。

  • 對於簡單的公開資料屬性,最好只公開屬性名稱,而不要使用複雜的存取/修改方法。請記住,Python 為未來的增強提供了簡單的途徑,如果您發現簡單的資料屬性需要增加功能行為。在這種情況下,請使用屬性 (properties) 將功能實作隱藏在簡單的資料屬性存取語法之後。

    注意 1:儘量保持功能行為無副作用,儘管快取等副作用通常是可以的。

    注意 2:避免對計算成本高昂的操作使用屬性 (properties);屬性符號會讓呼叫者認為存取是(相對)廉價的。

  • 如果您的類別打算被繼承,且您有不希望子類別使用的屬性,請考慮使用雙前導底線且無尾隨底線來命名它們。這會觸發 Python 的名稱改寫演算法,類別名稱會被改寫到屬性名稱中。這有助於避免在子類別無意中包含同名屬性時發生屬性名稱衝突。

    注意 1:請注意,在改寫後的名稱中僅使用簡單的類別名稱,因此如果子類別同時選擇了相同的類別名稱和屬性名稱,您仍然可能遇到名稱衝突。

    注意 2:名稱改寫會使某些用途(如偵錯和 __getattr__())變得不那麼方便。但名稱改寫演算法是有完整文件記錄的,且很容易手動執行。

    注意 3:並非每個人都喜歡名稱改寫。請嘗試平衡避免意外名稱衝突的需求與高階呼叫者潛在的使用需求。

公開與內部介面

任何向後相容性保證僅適用於公開介面。因此,使用者能夠清楚區分公開介面和內部介面非常重要。

有文件的介面被視為公開的,除非文件中明確宣告它們為暫時性或不受通常向後相容性保證約束的內部介面。所有未記錄的介面都應被視為內部介面。

為了更好地支援內省 (introspection),模組應使用 __all__ 屬性明確宣告其公開 API 中的名稱。將 __all__ 設定為空列表表示該模組沒有公開 API。

即使正確設定了 __all__,內部介面(套件、模組、類別、函式、屬性或其他名稱)仍應加上一個前導底線。

如果任何包含的命名空間(套件、模組或類別)被視為內部介面,那麼該介面也被視為內部介面。

匯入的名稱應始終被視為實作細節。其他模組不得依賴對此類匯入名稱的間接存取,除非它們是包含模組 API 中明確記錄的部分,例如 os.path 或公開子模組功能的套件 __init__ 模組。

程式設計建議

  • 編寫程式碼的方式不應損害其他 Python 實作(PyPy, Jython, IronPython, Cython, Psyco 等)。

    例如,不要依賴 CPython 對 a += ba = a + b 形式語句的就地字串串接的高效實作。這種最佳化即使在 CPython 中也很脆弱(僅適用於某些型別),並且在不使用引用計數的實作中根本不存在。在函式庫中對效能敏感的部分,應改用 ''.join() 形式。這將確保串接在各種實作中均能以線性時間執行。

  • 與 None 等單例 (singleton) 的比較始終應使用 isis not,絕不要使用相等運算子。

    此外,當您真正的意思是 if x is not None 時,要小心不要寫成 if x——例如在測試預設為 None 的變數或參數是否被設定為其他值時。該其他值可能具有在布林上下文中為假的型別(例如容器)!

  • 使用 is not 運算子而不是 not ... is。雖然兩個運算式在功能上相同,但前者更具可讀性,也是首選。
    # Correct:
    if foo is not None:
    
    # Wrong:
    if not foo is None:
    
  • 在使用豐富比較實作排序操作時,最好實作所有六個操作(__eq__, __ne__, __lt__, __le__, __gt__, __ge__),而不是依賴其他程式碼僅執行特定的比較。

    為了最小化工作量,functools.total_ordering() 裝飾器提供了一種產生缺失比較方法的工具。

    PEP 207 指出 Python 確實假定反射性規則。因此,直譯器可能會將 y > xx < y 互換,y >= xx <= y 互換,並可能會交換 x == yx != y 的參數。sort()min() 操作保證使用 < 運算子,而 max() 函式使用 > 運算子。然而,最好實作所有六個操作,以便在其他上下文中不會產生困惑。

  • 始終使用 def 語句,而不是將 lambda 運算式直接綁定到識別字的指派語句。
    # Correct:
    def f(x): return 2*x
    
    # Wrong:
    f = lambda x: 2*x
    

    第一種形式意味著所產生的函式物件名稱明確為 'f',而不是通用的 '<lambda>'。這對追蹤訊息 (tracebacks) 和一般字串表示更有幫助。使用指派語句消除了 lambda 運算式相較於明確的 def 語句所能提供的唯一好處(即它可以嵌入到更大的運算式中)。

  • Exception 而不是 BaseException 衍生異常。直接繼承 BaseException 是保留給幾乎總是錯誤處理方式的異常使用的。

    根據「擷取」異常的程式碼可能需要的區分點來設計異常層次結構,而不是根據觸發異常的位置。目標是透過程式設計回答「出了什麼問題?」的問題,而不是僅僅說明「發生了問題」(參見 PEP 3151,這是內建異常層次結構吸取此教訓的範例)。

    此處適用類別命名慣例,但如果異常是一個錯誤,您應該在異常類別上加上「Error」後綴。用於非本地流程控制或其他形式訊號傳遞的非錯誤異常不需要特殊的後綴。

  • 適當地使用異常鏈。應使用 raise X from Y 來明確表示替換,而不會丟失原始追蹤訊息。

    當故意替換內部異常時(使用 raise X from None),確保將相關細節傳輸到新異常中(例如在將 KeyError 轉換為 AttributeError 時保留屬性名稱,或者將原始異常的文字嵌入到新異常訊息中)。

  • 當擷取異常時,盡可能提及特定的異常,而不是使用空洞的 except: 子句。
    try:
        import platform_specific_module
    except ImportError:
        platform_specific_module = None
    

    空洞的 except: 子句會擷取 SystemExit 和 KeyboardInterrupt 異常,使人更難用 Control-C 中斷程式,並可能掩蓋其他問題。如果您想擷取所有標示程式錯誤的異常,請使用 except Exception:(空洞 except 等同於 except BaseException:)。

    一個很好的經驗法則是將空洞的 'except' 子句的使用限制在兩種情況下:

    1. 如果異常處理器將列印或記錄追蹤訊息;至少使用者會知道錯誤已經發生。
    2. 如果程式碼需要做一些清理工作,然後透過 raise 讓異常向上傳播。try...finally 可能是處理這種情況的更好方法。
  • 當擷取作業系統錯誤時,相較於內省 errno 值,請優先使用 Python 3.3 中引入的明確異常層次結構。
  • 此外,對於所有 try/except 子句,將 try 子句限制在絕對必要的最小程式碼量上。同樣,這可以避免掩蓋錯誤。
    # Correct:
    try:
        value = collection[key]
    except KeyError:
        return key_not_found(key)
    else:
        return handle_value(value)
    
    # Wrong:
    try:
        # Too broad!
        return handle_value(collection[key])
    except KeyError:
        # Will also catch KeyError raised by handle_value()
        return key_not_found(key)
    
  • 當資源對於程式碼的特定部分來說是局部的時,使用 with 語句來確保它在使用後得到及時且可靠的清理。try/finally 語句也是可以接受的。
  • 只要 Context Managers 除了取得和釋放資源之外做了其他事情,就應透過單獨的函式或方法來呼叫它們。
    # Correct:
    with conn.begin_transaction():
        do_stuff_in_transaction(conn)
    
    # Wrong:
    with conn:
        do_stuff_in_transaction(conn)
    

    後一個範例沒有提供任何資訊來表明 __enter____exit__ 方法在交易完成後除了關閉連線之外還在做其他事情。在這種情況下,明確表達很重要。

  • 在 return 語句中保持一致。函式中的所有 return 語句要麼都回傳一個運算式,要麼都不回傳。如果任何 return 語句回傳一個運算式,那麼任何未回傳值的 return 語句都應明確寫為 return None,並且在函式末尾(如果可到達)應存在明確的 return 語句。
    # Correct:
    
    def foo(x):
        if x >= 0:
            return math.sqrt(x)
        else:
            return None
    
    def bar(x):
        if x < 0:
            return None
        return math.sqrt(x)
    
    # Wrong:
    
    def foo(x):
        if x >= 0:
            return math.sqrt(x)
    
    def bar(x):
        if x < 0:
            return
        return math.sqrt(x)
    
  • 使用 ''.startswith()''.endswith() 而不是字串切片來檢查前綴或後綴。

    startswith() 和 endswith() 更簡潔,且不易出錯。

    # Correct:
    if foo.startswith('bar'):
    
    # Wrong:
    if foo[:3] == 'bar':
    
  • 物件型別比較應始終使用 isinstance(),而不是直接比較型別。
    # Correct:
    if isinstance(obj, int):
    
    # Wrong:
    if type(obj) is type(1):
    
  • 對於序列(字串、列表、tuple),利用空序列為假的特性:
    # Correct:
    if not seq:
    if seq:
    
    # Wrong:
    if len(seq):
    if not len(seq):
    
  • 不要撰寫依賴顯著尾隨空白的字串字面量。此類尾隨空白在視覺上無法區分,且某些編輯器(或最近的 reindent.py)會將其修剪掉。
  • 不要使用 == 將布林值與 True 或 False 進行比較。
    # Correct:
    if greeting:
    
    # Wrong:
    if greeting == True:
    

    更差的寫法:

    # Wrong:
    if greeting is True:
    
  • try...finally 的 finally 子句中使用流程控制語句 return/break/continue 是不被鼓勵的,因為這些流程控制語句會跳出 finally 子句。這是因為此類語句會隱式取消任何正在 finally 子句中傳播的活動異常。
    # Wrong:
    def foo():
        try:
            1 / 0
        finally:
            return 42
    

函數標註 (Function Annotations)

隨著 PEP 484 的採納,函式註解的風格規則已經改變。

  • 函式註解應使用 PEP 484 語法(上一節中有一些關於註解的格式化建議)。
  • 本 PEP 先前建議的對註解風格的實驗已不再受鼓勵。
  • 然而,在 stdlib 之外,現在鼓勵在 PEP 484 的規則內進行實驗。例如,使用 PEP 484 風格的型別註解標記大型第三方函式庫或應用程式,審查加入這些註解的難易程度,並觀察它們的存在是否增加了程式碼的可理解性。
  • Python 標準函式庫在採用此類註解時應持保守態度,但允許在新程式碼和大型重構中使用它們。
  • 對於希望對函式註解有不同用途的程式碼,建議在檔案頂部附近加上以下格式的註解:
    # type: ignore
    

    這會告知型別檢查器忽略所有註解。(更多停用型別檢查器投訴的精細方法可在 PEP 484 中找到。)

  • 與 linter 一樣,型別檢查器是選擇性的、單獨的工具。Python 直譯器預設不應因型別檢查而發出任何訊息,也不應根據註解改變其行為。
  • 不想使用型別檢查器的使用者可以自由忽略它們。然而,第三方函式庫套件的使用者預期可能會想要在這些套件上執行型別檢查器。為此,PEP 484 建議使用 stub 檔案:.pyi 檔案,型別檢查器會優先讀取這些檔案而非對應的 .py 檔案。Stub 檔案可以與函式庫一起散佈,或單獨(經函式庫作者許可)透過 typeshed 儲存庫散佈 [5]

變數註解

PEP 526 引入了變數註解。它們的風格建議與上述函式註解類似:

  • 模組層級變數、類別變數和實例變數,以及局部變數的註解,冒號後應有一個空格。
  • 冒號前不應有空格。
  • 如果指派語句有右側部分,則等號兩側應各有一個空格:
    # Correct:
    
    code: int
    
    class Point:
        coords: Tuple[int, int]
        label: str = '<unknown>'
    
    # Wrong:
    
    code:int  # No space after colon
    code : int  # Space before colon
    
    class Test:
        result: int=0  # No spaces around equality sign
    
  • 雖然 PEP 526 已被 Python 3.6 採納,但變數註解語法是所有 Python 版本中 stub 檔案的首選語法(詳細資訊請參閱 PEP 484)。

附註

參考文獻


來源:https://github.com/python/peps/blob/main/peps/pep-0008.rst

最後修改:2025-04-04 00:19:04 GMT