天天看點

PEP 8 – Python代碼風格指南(中文版)簡介枉曲直湊是小人的毛病代碼布局字元串引号表達式和語句中的空白何時使用尾随逗号注釋命名約定程式設計建議參考文獻版權聲明

PEP: 8
标題: Python代碼風格指南
作者: Guido van Rossum <guido at python.org>, Barry Warsaw <barry at python.org>, Nick Coghlan <ncoghlan at gmail.com>
狀态: 在行
類型: 流程
建立時間: 2001-07-05
釋出曆史: 2001-07-05,2013-08-01

目錄

簡介

枉曲直湊是小人的毛病

代碼布局

縮進

制表符還是空格?

最大行長度

斷行符應該在二進制操作符之前還是之後?

空行

源檔案編碼

導入

子產品級Dunder名稱

字元串引号

表達式和語句中的空白

惡習

其他建議

何時使用尾随逗号

注釋

塊注釋

内聯注釋

文檔字元串

命名約定

根本原則

描述性原則:命名風格

強制性原則:命名約定

應當避免的名稱

ASCII相容性

包和子產品的名稱

類的名稱

類型變量的名稱

異常的名稱

全局變量的名稱

函數和變量的名稱

函數和方法的參數

方法名稱和執行個體變量

常量

繼承的設計

公共和内部接口

程式設計建議

函數注解

變量注解

參考文獻

版權聲明

簡介

本文檔為那些由Python主流發行版的标準庫構成的Python代碼給出了編碼規範。您還可以參閱描述Python的C實作中的C代碼的風格準則的姊妹PEP。

本文檔和PEP 257(文檔字元串約定)都改編自Guido原先寫的Python Style Guide一文,并且補充了一些來自Barry的風格指南的内容。[2]

由于Python語言本身的變化,這個風格指南也會随着時間的推移而逐漸演變,一些新的約定會被确定,而一些舊的約定則會過時。

許多項目有它們自己的編碼風格準則,如果這些準則與本文有所沖突,請在那個項目中優先遵守項目特定的準則。

枉曲直湊是小人的毛病

Guido的關鍵見解之一是:代碼被閱讀的次數比其被編寫的次數更多。這裡提供的準則旨在提高代碼的可讀性,并使大環境下的Python代碼風格保持一緻。正如PEP 20所說,“可讀性至關重要”。

風格指南是關于一緻性的文本。與本風格指南保持一緻很重要,但項目内的一緻性更重要、一個子產品或函數内的一緻性最重要。

然而,要知道何時應該不一緻——有時候風格指南的建議正好是不适用的。當你對此困惑時,采用你的最佳判斷下的方案。仔細看看其他例子的處理方法,再決定哪種做法看起來最好。而且,有困惑時不要猶豫,盡管向别人提問!

特别注意:不要僅僅為了遵守這個PEP就破壞向後相容性!

忽視某條準則的其他一些充分理由:

  1. 即使對于那些習慣閱讀遵循了本PEP的代碼的人來說,當應用該準則會降低代碼的可讀性時。
  2. 與工作環境中的代碼保持一緻就會打破該準則(可能是由于曆史原因)——盡管(在真正的XP*風格中)這也是個收拾别人爛攤子的好機會。
  3. 因為題中所述代碼的完成時間早于該準則的首發時間,而且現在沒有别的理由需要修改它。
  4. 當這段代碼需要保持對舊版本的Python相容,且該版本Python不支援這條準則所推薦使用的特性時。

*Extreme Programming,極限程式設計,譯者注。

代碼布局

縮進

每級縮進使用4個空格。

無論是在小括号、中括号和大括号裡面垂直地使用Python的隐式行連接配接還是使用懸挂縮進[1],都應該對齊連續行内的被包裹元素。使用一個懸挂縮進時應考慮到以下要點;首行應當沒有任何參數,且後續行應當使用更多的縮進來凸顯它和首行的差別:

# 正确:



# 與起始定界符對齊

foo = long_function_name(var_one, var_two,

                         var_three, var_four)



# 添加4個空格(額外的一級縮進)以區分參數和後續内容。

def long_function_name(

        var_one, var_two, var_three,

        var_four):

    print(var_one)



# 懸挂縮進應當添加一級縮進。

foo = long_function_name(

    var_one, var_two,

    var_three, var_four)

# 錯誤:



# 當不使用垂直對齊時,首行禁止出現參數。

foo = long_function_name(var_one, var_two,

    var_three, var_four)



# 縮進不夠顯著時需要更多縮進。

def long_function_name(

    var_one, var_two, var_three,

    var_four):

    print(var_one)
           

對連續行而言,4空格規則是非強制的。

非強制的情況:

# 懸挂縮進*可能*縮進為4個空格以外的其他量。

foo = long_function_name(

  var_one, var_two,

  var_three, var_four)
           

當if語句的條件部分太長以至于需要分多行編寫時,值得注意的是,一個由兩個字元組成的關鍵字(即if)加上一個空格、再加上一個開的小括号就為多行條件的後續行建立了一個自然的4空格縮進。這樣可以為嵌套在if語句中本該縮進4個空格的縮進代碼塊制造視覺沖擊。這個PEP沒有對怎樣(或者是否應當)進一步在視覺上區分這種if語句的嵌入語塊中的條件行作出明确解釋。可選的處理方式包括但不限于:

# 沒有額外的縮進。

if (this_is_one_thing and

    that_is_another_thing):

    do_something()



# 添加一個注釋,這樣就能在支援文法高亮的編輯器中增加一點辨識度。



if (this_is_one_thing and

    that_is_another_thing):

    # 當兩個條件都為真時,我們可以開始惡搞。

    do_something()



# 為條件部分的連續行添加額外的縮進。

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',

)
           

制表符還是空格?

空格是首選的縮進方法。

制表符隻用于與那些早已使用制表符來縮進的代碼來保持一緻。

Python不允許混用制表符和空格來縮進。

最大行長度

所有行都限制為最多含有79個字元。

對于那些較少結構限制的可流動的長文本塊(文檔字元串或者注釋),其行長度應當限制到72個字元。

限制編輯器所需的視窗寬度讓你能夠同時打開幾個檔案,并且讓你得以正常使用那些并列展示兩個版本的代碼審查工具。

大多數工具的預設折疊會破壞代碼的可視結構,讓代碼更難以了解。選擇這樣的限制上限之後,即使編輯器在折疊代碼行時會在最後一列添加标記,也能避免代碼在視窗寬度設定為80的編輯器中産生折疊。當然,一些基于web的工具可能根本不具備動态折疊代碼的功能。

一些團隊特别衷愛更長的行長度。如果代碼主要或專門由一個部可以對此問題達成一緻團隊來維護,那麼把行長度的上限增加到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())
           

(參見之前關于多行if語句的讨論并進一步思考類似的with語句的縮進。)

另一個像這樣的例子是assert語句。

確定對連續行進行适當縮進。

斷行符應該在二進制操作符之前還是之後?

數十年來的推薦風格都是在二進制操作符之後斷行,但是這樣會從兩方面降低代碼的可讀性:一是二進制操作符會傾向于分布在螢幕上的不同列,二是每個操作符會從對應的操作數那裡被移走并放到上一行。如此一來,你不得不費更多工夫來辯别哪一項被加、哪一項被減:

# 錯誤

# 操作符位于遠離被操作數的位置。

income = (gross_wages +

          taxable_interest +

          (dividends - qualified_dividends) -

          ira_deduction -

          student_loan_interest)
           

為了解決這個影響可讀性的問題,數學家及其編輯們采用了與上文相反的約定Donald Knuth在他Computers and Typesetting series這本書中對此作出了解釋:“盡管行内公式通常在二進制操作符和關系符号之後斷行,但行間公式卻總是在二進制操作符之前斷行”[3]。

遵循這一來自數學家的傳統之後,通常可以編寫出可讀性更好的代碼:

# 正确:

# 易于比對二進制操作符及其被操作數。

income = (gross_wages

          + taxable_interest

          + (dividends - qualified_dividends)

          - ira_deduction

          - student_loan_interest)
           

Python代碼中,隻要和原先約定保持一緻,在二進制操作符之前或之後斷行都是允許的。但對于新編寫的代碼,我們推薦采用Knuth的風格。

空行

頂級函數和頂級類的定義之間用兩個空行隔開。

類内部的方法定義之間用一個空行隔開。

可以(謹慎地)用額外的空行對相關的函數進行分組。應該省略一組相關的單行代碼(例如一組虛拟實作的語句)中的空行。

在函數之間少量使用空行來指出邏輯上關聯的部分。

Python接受将control-L(即^L)換頁符作為空白使用,許多工具将其視為頁面分隔符号,是以你可以用它來對你檔案中的關聯部分進行分頁。注意,一些基于web的代碼閱讀器可能不會将control-L視為換頁符,而是在這個位置顯示别的标記。

源檔案編碼

Python核心發行版中的代碼應該始終使用UTF-8編碼,而且不應有任何編碼聲明。

在标準庫中,非UTF-8編碼應當僅作測試之用。

謹慎使用非ASCII字元,最好僅将其用于表示地名和人名。如果使用了非ASCII字元作為資料,避免使用像“z̯̯͡a̧͎̺l̡͓̫g̹̲o̡̼̘”這樣的噪聲Unicode字元和位元組順序标記。

Python标準庫中的所有辨別符都必須是僅含ASCII字元的辨別符,而且應該在可行的地方都使用英文(在許多情況下,縮寫和術語使用的并非英語)。

開源項目面向全球,鼓勵采用與前文所述相似的編碼政策。

導入

  • 導入通常應當被編寫在分立的行上:
# 正确:

import os

import sys

# 錯誤:

import sys, os
           

這麼做也是可以的:

# 正确:

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.MyClass”和“foo.bar.yourclass.YourClass”。

  • 應當避免使用通配符導入(from <module> import *),因為這樣會讓人搞不清楚命名空間中存在哪些名稱,讓讀者和自動工具都難以了解。有一個使用通配符導入的特例,那就是将内部接口作為公共API的一部分重新釋出時(例如,選用一個加速子產品的定義來覆寫一個純Python實作的接口時,哪些定義會被覆寫是事先無法預料的)。

用這種方式重新釋出名稱時,仍然适用以下關于公共接口和内部接口的準則。

子產品級Dunder名稱

子產品級“Dunder*”(即那些前後都有兩條下劃線的名稱)如__all__、__author__、__version__等,都應當放在文檔字元串之後,以及除了from __future__ 導入以外的導入語句之前。Python要求子產品中的future導入必須出現在除了文檔字元串以外的代碼之前:

"""這是示例子產品。



這個子產品具有一定功能。

"""



from __future__ import barry_as_FLUFL



__all__ = ['a', 'b', 'c']

__version__ = '0.1'

__author__ = 'Cardinal Biggles'



import os

import sys
           

*“dunder”可意譯為“雙下”,即“雙下劃線(double underlines)”的簡稱,國内一般譯為“魔法變量/魔法函數”、“魔術變量/魔術函數”等。

字元串引号

在Python中,單引号字元串和雙引号字元串本質上是一樣的。PEP對此不作建議,標明一個規矩并堅持使用就好。不過,當字元串中包含單引号或雙引号字元時,通過使用另一種引号表示字元串來避免在字元串中使用反斜杠可以提高可讀性。

對于三引号字元串,應當始終使用雙引号字元來與PEP 257中的文檔字元串約定保持一緻。

表達式和語句中的空白

惡習

應當避免在下列情形中出現多餘的空格:

  • 緊跟小括号、中括号、大括号的内部:
# 正确:

spam(ham[1], {eggs: 2})

# 錯誤:

spam( ham[ 1 ], { eggs: 2 } )
           
  • 尾随逗号和其後的閉括号之間:
# 正确:

foo = (0,)

# 錯誤:

bar = (0, )
           
  • 緊跟逗号、分号或冒号之前:
# 正确:

if x == 4: print x, y; x, y = y, x

# 錯誤:

if x == 4 : print x , y ; x , y = y , x
           
  • 不過,冒号在切片中的作用類似二進制操作符,其兩側應當有等量的空格(把它當作一個優先級最低的操作符)。在拓展切片中,兩個分号都應該賦予等量的空格。例外:如果切片中省略了某個參數,則對應空格也應省略:
# 正确:

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]

# 錯誤:

ham[lower + offset:upper + offset]

ham[1: 9], ham[1 :9], ham[1:9 :3]

ham[lower : : upper]

ham[ : upper]
           
  • 緊跟函數調用的參數清單開頭的開括号之前:
# 正确:

spam(1)

# 錯誤:

spam (1)
           
  • 緊跟索引或切片開頭的開括号之前:
# 正确:

dct['key'] = lst[index]

# 錯誤:

dct ['key'] = lst [index]
           
  • 指派(或其他)操作符周圍有多個空格以使其與另一個操作符對齊:
# 正确:

x = 1

y = 2

long_variable = 3

# 錯誤:

x             = 1

y             = 2

long_variable = 3
           

其他建議

  • 避免在任何地方使用尾随空白。因為尾随空白它通常難以察覺,而且容易造成困擾:例如一個反斜杠後接一個空格和一個新行,那它就不算續行符了。有些編輯器不排斥這種做法,但許多有pre-commit hooks*的項目(比如CPython本身)會駁回這種做法。

*預送出鈎子,用于代碼檢查,譯者注。

  • 始終在這些二進制操作符兩側放一個空格:指派号(=)、增量指派号(+=、-=等), 比較符号(==、<、>、!=、<>、<=、>=、in、not in、is、is not)、布爾運算(and、or、not)。
  • 如果使用不同優先級的操作符,可以考慮在最(較)低優先級的操作符周圍添加空格。具體情況自行判斷,但切勿使用多個空格,并且二進制運算符的兩側應當始終具有數量相同的空格:
# 正确:

i = i + 1

submitted += 1

x = x*2 - 1

hypot2 = x*x + y*y

c = (a+b) * (a-b)

# 錯誤:

i=i+1

submitted +=1

x = x * 2 - 1

hypot2 = x * x + y * y

c = (a + b) * (a - b)
           
  • 函數的聲明應當使用冒号的一般規則,如果出現箭頭->的話,應始終確定箭頭前後有空格。(更多關于函數聲明的内容參見後續的函數注解):
# 正确:

def munge(input: AnyStr): ...

def munge() -> PosInt: ...

# 錯誤:

def munge(input:AnyStr): ...

def munge()->PosInt: ...
           
  • 指出關鍵詞的參數值或給未注解的函數參數設定預設值時,不要在=符号前後使用空格:
# 正确:

def complex(real, imag=0.0):

    return magic(r=real, i=imag)

# 錯誤:

def complex(real, imag = 0.0):

    return magic(r = real, i = imag)
           

但用于連接配接有預設值的參數注解時,要在在=符号前後使用空格:

# 正确:

def munge(sep: AnyStr = None): ...

def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...

# 錯誤:

def munge(input: AnyStr=None): ...

def munge(input: AnyStr, limit = 1000): ...
           
  • 一般不鼓勵使用複合語句(同一行内有多個語句的語句):
# 正确:

if foo == 'blah':

    do_blah_thing()

do_one()

do_two()

do_three()
           

而不是:

# 錯誤:

if foo == 'blah': do_blah_thing()

do_one(); do_two(); do_three()
           
  • 但有時把短的if/for/while語句放在同一行也是可以的,但對多從句的語句就别這麼做,反而還要避免折疊這麼長的行!

而不是:

# 錯誤:

if foo == 'blah': do_blah_thing()

for x in lst: total += x

while t < 10: t = delay()
           

更不應該:

# 錯誤:

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()
           

何時使用尾随逗号

尾随逗号通常是非強制的,除非在建立一個元素的元組時才是強制的。為了清楚起見,建議将後者用括号括起來(技術上是多餘的):

# 錯誤:

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()
           

在使用版本控制系統的情況下,如果值、參數或者導入項需要随着時間的推移而進行拓展,備援的尾随逗号一般都很有用。操作方法就是将每個值(之類的)單獨放一行,并始終對其追加一個尾随逗号,最後在序列的最後一行添上閉的小括号、中括号或大括号。但是尾随逗号和結束定界符在同一行是沒有意義的(除了上述單元組的情況):

# 正确:

FILES = [

    'setup.cfg',

    'tox.ini',

    ]

initialize(FILES,

           error=True,

           )

# 錯誤:

FILES = ['setup.cfg', 'tox.ini',]

initialize(FILES, error=True,)
           

注釋

與代碼相沖突的注釋比沒有注釋更糟糕。當代碼更新時,要始終優先保證注釋也是最新的!

注釋應當是完整的句子。注釋的首字母應當大寫,除非句子的第一個單詞是以小寫字母開頭的辨別符(千萬不要改變辨別符的大小寫!)。

塊注釋通常由一個或多個以整句為基礎的段落組成,其中每個句子以句号結尾。

在多句注釋中,除了最後一句之外,你應當每一句結束的句号後加入兩個空格。

確定你寫的注釋對于你所用語言*的其他使用者來說清晰易懂。

來自非英語國家的Python程式員:請用英語書寫你的注釋,除非你120%确定你的代碼不會被那些不懂你的語言的人讀到。

*特指英語、漢語等在社會交際中使用的自然語言,而不是用于人機通信的程式設計語言,譯者注。

塊注釋

塊注釋一般用于注釋其後續的部分(或全部)代碼,并且和被注釋代碼使用相同的縮進等級。塊注釋的每一行都以#和一個空格開頭(除非注釋中有縮進文本)。

塊注釋中的段落用含有一個#的空行分隔。

内聯注釋

盡量少用内聯注釋。

内聯注釋是和語句在同一行上的注釋。應當使用至少兩個空格來将内聯注釋和前面的語句分隔開。内聯語句應當以一個#和一個空格開頭。

如果語句顯而易見的話,内聯注釋就是不必要的,而且它反而會讓人分心。不要這樣做:

x = x + 1                 # 累加 x
           

但有時,這麼做挺有用:

x = x + 1                 # 彌補邊框
           

文檔字元串

寫好的文檔字元串(又叫“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的差別在于mixedCase的第一個字母是小寫!)
  • Capitalized_Words_With_Underscores(單詞首字母大寫帶下劃線,真醜!)

還有一種用簡短而獨特的字首來把相關的名稱組合在一起的風格。這種風格在Python中用得不多,不過還是順帶提一下,這種做法是為了完整性。例如,os.stat()函數會傳回一個元組,其元素的名稱通常有像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(雙前導下劃線): 當命名類的特性時,調用名稱矯正(類FooBar中,__boo會變成_FooBar__boo;更多請參見下文)。
  • __double_leading_and_trailing_underscore__(雙前導下劃線和尾随下劃線):使用者控制的命名空間中的“魔術”對象或特性。例如__init__、__import__或__file__。不要自己創造這樣的名稱,隻能使用已記錄的那些雙下劃線名稱。

強制性原則:命名約定

應當避免的名稱

不要單獨使用“l”(字母L的小寫)、“O”(字母o的大寫)或者“I”(i的大寫字母)這樣的字元作為變量名稱。

在有一些字型中,這些字元很難和數字1跟0區分開來。打算用“l”的時候,用“L”來代替。

ASCII相容性

标準庫中使用的辨別符必須如PEP 3131中描述的方針部分那樣與ASCII相相容。

包和子產品的名稱

子產品應當用短、全小寫的名稱。如果可以提高可讀性的話,也可以在子產品名中使用下劃線。Python包也應當使用短、全小寫的名稱,但不推薦使用下劃線。

如果一個用C或者C++編寫的子產品附帶一個提供了更進階(例如面向更多對象)接口的Python子產品,那個C/C++子產品的名稱應當有前導下劃線(例如_socket)。

類的名稱

類的名稱通常使用CapWords的約定。

在接口被記錄且主要用于可調用對象的情況下,可以用函數的命名約定來代替類的命名約定。

注意,内置名稱有其獨立的約定:因為CapWords的約定僅适用于異常名的稱和内置常數的名稱,絕大多數内置名稱是單詞(或者幾個詞連在一起)。

類型變量的名稱

相比短名稱,PEP 484中所述類型變量的名稱一般應當使用CapWords風格:T、AnyStr、Num。建議給變量添加字尾來指出其在協變或逆變行為上的一緻性:

from typing import TypeVar



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

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

異常的名稱

因為異常是一種類,類的命名約定适用于此。但是你應當使用“Erro”作為你的異常名稱的字尾(如果那個異常确實是一個錯誤的話)。

全局變量的名稱

(我們首先假設這些變量僅在一個子產品内部使用。)這些規定跟函數的是一樣的。

計劃通過from M import *來使用的子產品應該使用__all__機制來防止導出全局變量,或者使用更舊的約定,即在這些(你想表明是“子產品的而非公共的”的)全局變量前面添加下劃線(你可能想要這樣做,以表明這些全局變量是“子產品非公共的”)。

函數和變量的名稱

函數名應當用lowercase的約定,必要時可以添加下劃線分隔單詞來提高可讀性。

變量名稱遵循和函數名稱一樣的約定。

mixedCase風格隻允許在已經流行該風格的上下文(如threading.py)中使用,以保持向後相容性。

函數和方法的參數

始終為執行個體方法的第一個參數使用關鍵字self。

始終為類方法的第一個參數使用關鍵字cls。

如果一個函數的參數名稱與保留關鍵字相沖突,一般最好是在這個參數關鍵字後面加下劃線,而不是使用縮寫或者錯誤的拼寫。是以class_的命名方式比clss更好。(或許用近義詞來避免沖突更好。)

方法名稱和執行個體變量

使用函數的命名規則: lowercase以及必要時提高可讀性的下劃線。

僅對非公共方法和執行個體變量使用前導下劃線。

為了避免名稱和子類相沖突,使用雙前導下劃線來調用Python的名稱矯正規則。

Python會矯正那些和類名稱沖突的名稱:如果Foo類有一個叫__a的特性的話,它就不會被Foo.__a通路。(固執的使用者還仍然可以通過調用Foo._Foo__a來通路該特性。)一般,雙前導下劃線僅用于避免和那些子類專屬的特性相沖突。

注意:使用__名稱是有所争議的(詳見下方)。N

常量

常量通常在子產品級進行定義,并用帶有分詞下劃線的全大寫字母來表示。例如MAX_OVERFLOW和TOTAL。

繼承的設計

始終确定好類的方法和執行個體變量(包括“特性”)是否應該公共化。如果不太确定的話,選擇不共化,後續把一個特性改為共化比改為不共化要麻煩。

公共特性是給那些與你的類不太相關的客戶所使用的特性,你得確定它們不會發生向後相容不相容的變更。非公共特性是那些不打算被第三方使用的特性,你不必確定非公共特性将來不會改變或者被移除。

在這裡,我們不使用“private(私有的)”,因為在Python中沒有任何一個(未經過大量必要工作處理過的)變量是真正私有的。

另一種特性是那種“子類API”的一部分(在其他語言裡一般稱為“protected(保護特性)”)。有些類就是用來繼承的,要麼拓展要麼更改這些類某方面的行為。設計這些類的時候,注意明确哪些特性是公共特性、哪些特性是子類API的一部分、哪些是真正僅供你的基礎類使用的特性。

出于這種考慮,Python制定了以下準則:

  • 公共特性不應有任何前導下劃線。
  • 如果公共特性與保留字有沖突,則為你的特性名稱添加一個尾随下劃線。這種做法比用縮寫或者錯誤的拼寫要更好。(然而,盡管有這個規則,對于已經為人所熟知的任何代指一個類的變量或參數,尤其是對于類方法的第一個參數而言,“cls”還是其首選的拼寫方式。)

注1:參見以上關于類方法的參數名稱推薦的讨論。

  • 對于簡單的公共資料特性,最好隻公開特性名稱,而不要涉及複雜的通路器/更改器方法。如果您發現一個簡單的資料特性需要發展功能性的行為,請記得Python為其後續增強提供了簡單的途徑。在這種情況下,請使用屬性來隐藏簡單資料特性通路語句背後的功能實作。

注1:盡量保持功能性的行為沒有副作用,盡管像這樣的副作用緩存通常是好的。

注2:避免對計算開銷較大的操作使用屬性,因為那個特性符号*會使得通路器認為這個通路的開銷(相對)較小。

*“特性符号”即點符号,又稱“點标記”,譯者注。

  • 如果你的類會被子類化,而且你還有一些不希望子類使用的特性,那麼可以考慮用含有雙前導下劃線而不含尾随下劃線的方式來命名這些類。這樣會調用Python的名稱矯正算法,這個算法會把類的名稱矯正為特性的名稱。如果子類在無意中使用了與特性一樣的名稱,這麼做可以避免其與特性名稱的沖突。

注1:注意名稱矯正僅用于對簡單的類名稱進行矯正,是以如果一個子類同時用了已有類和特性的名稱,那還是會發生名稱沖突。

注2:名稱矯正會給諸如調試和_getattr__()這樣的特定用途帶來一定不便。不過名稱矯正算法有重組的文檔支援,而且易于手動執行。

注3:并不是所有人都喜歡名稱矯正。類的名稱盡量在避免異常名稱沖突的需求和被進階調用者所使用的潛在需求之間取得平衡。

公共和内部接口

確定任何向後相容性隻應用于公共接口。相應地,使用者能夠清楚地分辨公共和内部接口也很重要。

文檔所述的接口都理應是公共接口,除非描述文檔明确地宣稱它們是臨時接口或内部接口,不受通常的向後相容性保證的限制。所有未文檔化的接口都應當假定是内部接口。

為了更好地支援自省機制,子產品應當使用__all__特性來明确地指出其屬于公開API的名稱。将__all__設定為一個空清單則表明那個子產品沒有公共API。

即使合理地設定了__all__特性,内部接口(包、子產品、類、函數、特性或者其他對象的名稱)也都應當以一個前導下劃線作為字首。

如果其包含的命名空間(包、子產品或類)中有元素被視為内部元素,則該接接口也将被視為内部接口。

應當始終把導入的名稱當作子產品的一個實作細節來看待。其他子產品不能依賴于間接通路這些導入的名稱來實作,除非這些名稱在子產品包含的API文檔中被明确記載要這麼使用,例如os.path的名稱或者包中用于公開子子產品功能的__init__子產品的名稱。

程式設計建議

  • 應當用以一種不會損害其他Python實作(如PyPy、Jython、IronPython、Cython、Psyco)的方式來編寫代碼。
  • 例如,對a += b或a = a + b形式的語句,不要依賴CPython就地字元串連接配接的有效實作。即使在CPython中,這種優化也是脆弱的(它隻對某些類型起作用),而且在不使用引用計數的實作中也根本不存在這種優化。庫中性能容易受影響的部分應使用“.join()”形式來代替就地字元串連接配接。這樣就能確定不同實作之間的串聯發生線上性時間内。
  • 像None這樣的單值之間的比較,應當始終使用is或者is not來進行,而不要使用相等操作符。
  • 而且,判斷一個預設值是None的變量或參數是否已經被設定為其他值的時候——例如當你真的想表達if x is not None的意思時——注意if x的書寫。其他值可能是一種會在布爾上下文中表現為假的資料類型(比如容器類型)。
  • 使用is not操作符而不要使用not ... is操作符。雖然這兩個表達式的功能是相同的,但前者可讀性更好也更好:
# 正确:

if foo is not None:

# 錯誤:

if not foo is None:
           
  • 當實作含有大量比較的排序操作時,最好實作所有六個操作符(__eq__、__ne__、__lt__、__le__、__gt__、__ge__)而不是依靠其它代碼而隻能進行某個特定的比較。

為了減小比較所涉及的計算量,functools.total_ordering()裝飾器為生成Python所缺少的比較方法提供了一種手段。

PEP 207表明,自反性規則被Python所采用。是以,編譯器可能會将y > x轉換為x < y、y >= x轉換為x <= y,而且可能會轉換x == y和x != y的參數。sort()和min()操作可以確定使用<操作符,而max()函數則確定使用>操作符。盡管如此,最好還是實作全部六個操作,以免在在其他上下文中出現混亂。

  • 始終使用def語句而不要使用直接在辨別符後面帶一個lambda表達式的指派語句:
# 正确:

def f(x): return 2*x

# 錯誤:

f = lambda x: 2*x
           

第一種形式意味着所得的函數對象的名稱是具體的“f”,而不是一般性的“<lambda>”。一般來說,在回溯和字元串表示時,這麼做更有用。指派語句的使用抹殺了lambda表達式相對于顯式def語句的唯一好處(即它可以嵌入到更大的表達式中)

  • 從Exception而不是BaseException中派生異常。直接從BaseException中繼承的手段是為那些一旦捕捉就幾乎意味着犯錯的異常而保留的。

根據捕獲異常的代碼可能需要的差别來設計異常的層次結構,而不是根據異常被抛出的位置來設計。這麼設計的目的在于用程式設計的方式回答“出了什麼問題?”,而不是僅僅聲明“發生了一個問題”(請參閱PEP 3151以學習本經驗關于内建異常層次結構示例)。

類的命名約定也适用于此,不過,如果那個異常确實是一個錯誤的話,你還是應當為你的異常類添加“Erro”字尾。用于非本地流程控制或者其他形式的信号處理的、非錯誤的異常不需要特殊的字尾。

  • 合理使用異常鍊。raise X from Y可被用于在不丢失原始回溯資訊的同時明确地指出需要修改的代碼。

當有意要(使用raise X from None)替換一個内部異常,務必確定相關的細節也被轉移到了新的異常中(比如在将KeyError轉換 AttributeError時,保留特性名,或者在新的異常消息中嵌入原始異常的文本)。

  • 捕捉異常時,要盡可能提及詳盡的異常,而不要使用空的except:子句:
try:

    import platform_specific_module

except ImportError:

    platform_specific_module = None
           

一個空的except:子句會捕捉 SystemExit和SystemExit類型的錯誤,導緻難以使用Control-C來中斷程式,而且會掩蓋程式中存在的其他問題。如果你想捕捉所有導緻程錯誤的異常,請使用except Exception:(空的except相當于except BaseException:)。

一個很好的經驗法則是将空“except”子句限定在以下兩種情況中使用:

  1. 如果異常處理程式會列印或記錄回溯資訊;或者至少使用者能察覺到有錯誤發生。
  2. 如果代碼需要做一些清理工作,但是随後用raise抛出異常。這種情況用try...finally處理更好。
  • 當捕捉作業系統的錯誤時,最好使用Python3.3中引進的顯式異常層次,而不是使用errno值的自省。
  • 此外,對于所有try/except子句,限制try子句的數量至代碼所需的絕對最少量。同理,這樣也能防止代碼中藏有bug:
# 正确:

try:

    value = collection[key]

except KeyError:

    return key_not_found(key)

else:

    return handle_value(value)

# 錯誤:

try:

    # try的内容太寬泛了!

    return handle_value(collection[key])

except KeyError:

    # 不過依舊能捕捉從handle_value()抛出的KeyError異常。

    return key_not_found(key)
           
  • 當一個資源是某個特定代碼段的局部時,請在它使用過後運用with語句來確定迅速可靠地将其清理掉。這種情況下也可以使用try/finally語句。
  • 無論何時做了擷取或釋放資源以外的其他操作,都應當通過單獨的函數或方法調用上下文管理器。例如:
# 正确:

with conn.begin_transaction():

    do_stuff_in_transaction(conn)

# 錯誤:

with conn:

    do_stuff_in_transaction(conn)
           

後面的例子并沒有提供任何資訊來表明__enter__和__exit__方法除了在事務之後關閉連接配接還做了什麼額外工作。在這種場合,稍加明确是很重要的。

  • 傳回語句之間要保持一緻。函數中的所有傳回語句要麼都有傳回值,要麼都沒有傳回值。隻要存在一個傳回語句有傳回值,則任意一個沒有傳回值的傳回語句都應該明确地以return None來表述該情況,并且應當把顯式傳回語句放在函數末尾(如果可以):
# 正确:



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)

# 錯誤:



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()表達起來更清晰,并且更能漸少錯誤:

# 正确:

if foo.startswith('bar'):

# 錯誤:

if foo[:3] == 'bar':
           
  • 應當始終使用isinstance()進行對象類型的比較,而不是直接比較類型 :
# 正确:

if isinstance(obj, int):

# 錯誤:

if type(obj) is type(1):
           
  • 對序列(如字元串、清單、元組)作判斷時,利用好空序列為假的事實:
# 正确:

if not seq:

if seq:

# 錯誤:

if len(seq):

if not len(seq):
           
  • 不要書寫依賴重要尾随空格的字元串文字。這樣的尾随空格在視覺上難以識别,一些編輯器(或者最近的reindent.py)會對這些空格進行裁剪。
  • 不要用==将布爾值與True或False進行比較:
# 正确:

if greeting:

# 錯誤:

if greeting == True:
           

更壞的做法:

# 錯誤:

if greeting is True:
           
  • 不鼓勵在try...finally的finally套件中使用return/break/continue這幾個會導緻流程控制語句跳出finally套件的控制語句。這是因為這樣的語句會隐式地中正在finally套件中傳遞的現用異常:
# 錯誤:

def foo():

    try:

        1 / 0

    finally:

        return 42
           

函數注解

随着PEP 484的采用,函數注解的風格規則已經變更:

  • 函數注解應當使用PEP 484文法(前面的章節已經對函數注解的格式提出了一些建議)。
  • 不再鼓勵實踐之前在本PEP中推薦的注解風格。
  • 但是,現在鼓勵在标準庫之外的場合實踐PEP 484的規則。例如,用PEP 484風格的注解來标記大型第三方庫或者應用程式、反思添加這些注解是多麼容易、觀察注解的存在是否提高了代碼的易懂性。
  • Python的标準庫應當保守地采用這樣的注解,但可以在新代碼和大型重構中(廣泛地)使用它們。
  • 對于想要以不同方式來使用函數注解的代碼,建議在靠近檔案頂行的位置添加如下格式的注釋:
# type: ignore
           

這個注釋告訴類型檢查器忽略所有注解。(在PEP 484中可以找到更精确的方法來禁用類型檢查器的回報。)

  • 和Linter*一樣,類型檢查器也是一個可選的、獨立的工具。在預設情況下,Python解釋器不會由于類型檢查而發送任何消息,也不會根據注釋更改其行為。

*一款Python代碼檢測插件,譯者注。

  • 不想使用類型檢查器的使用者可以随意忽略它們。然而,第三方庫的使用者們可能希望對這些包進行類型檢查程式。為此,PEP 484建議使用存根檔案——即.pyi檔案,類型檢查器會優先讀取這些檔案,而不是相應的.py檔案。存根檔案可以與庫一起分發,也可以(在庫作者的許可下)通過typeshed repo[5]單獨分發。

變量注解

PEP 526 引入了變量注解。其風格建議和前文描述的函數注解是一樣的:

  • 子產品級變量、類和執行個體變量,以及本地變量的注解都應當在冒号之後留一個空格。
  • 冒号前不應有任何空格。
  • 如果一個指派号有右側項的話,則等号的兩側都應該有一個空格:
# 正确:



code: int



class Point:

    coords: Tuple[int, int]

    label: str = '<unknown>'

# 錯誤:



code:int  # 冒号後沒有空格

code : int  # 冒号前有空格



class Test:

    result: int=0  # 指派号周圍沒有空格。
           
  • 雖然PEP 526從Python 3.6才開始采用,但在所有版本的Python中,變量注解的文法是存根檔案的首選文法(更多詳情請參見PEP 484)。

腳注

[1] 懸挂縮進是一種排版風格,該風格中段落除第一行以外的所有行都縮進。在Python的上下文中,這個術語用于描述這樣一種樣式,即被括号包覆的語句的開始括号是行的最後一個非空白字元,随後直到結束括号的行都被縮進。

參考文獻

[2] Barry的GNU Mailman風格指南http://barry.warsaw.us/software/STYLEGUIDE.txt
[3] Donald Knuth的The TeXBook,195頁和196頁
[4] http://www.wikipedia.com/wiki/CamelCase
[5] Typeshed repo GitHub - python/typeshed: Collection of library stubs for Python, with static types

版權聲明

本文檔已歸屬于公共領域。

源檔案:https://github.com/python/peps/blob/master/pep-0008.txt