轉(zhuǎn)自https://zhuanlan.zhihu.com/p/702181903
Python 規(guī)范主要指的是編寫 Python 代碼時(shí)應(yīng)遵循的一系列編程風(fēng)格指南,其中最著名和廣泛接受的是PEP 8(Python Enhancement Proposal 8)。PEP 8 是由 Python 社區(qū)制定的官方風(fēng)格指南,旨在提升代碼的可讀性和一致性。
規(guī)范要點(diǎn)
代碼布局
使用4個(gè)空格進(jìn)行縮進(jìn),而不是制表符。
每行代碼長(zhǎng)度建議不超過79字符,若超過則適當(dāng)換行。
在頂級(jí)定義之間空兩行,在方法定義之間空一行。
當(dāng)行過長(zhǎng)需要續(xù)接到下一行時(shí),可以在運(yùn)算符后換行,并在新行進(jìn)行適當(dāng)縮進(jìn)。
命名規(guī)則
變量名、函數(shù)名應(yīng)使用小寫字母和下劃線(snake_case)。
類名使用駝峰命名法(CapWords或CamelCase)。
常量全大寫,單詞間用下劃線分隔。
導(dǎo)入語(yǔ)句
導(dǎo)入語(yǔ)句應(yīng)放在文件開頭,先標(biāo)準(zhǔn)庫(kù)導(dǎo)入,后第三方庫(kù)導(dǎo)入,最后是本地應(yīng)用/模塊導(dǎo)入。
每個(gè)導(dǎo)入語(yǔ)句應(yīng)單獨(dú)一行,可以使用括號(hào)來(lái)分組多條導(dǎo)入語(yǔ)句。
字符串引號(hào)
優(yōu)先使用單引號(hào)(' '),除非字符串中包含單引號(hào),此時(shí)使用雙引號(hào)(" ")。
多行字符串使用三引號(hào)('''或""")。
空格使用
在操作符兩邊各加一個(gè)空格:a = b + c。
不要在逗號(hào)、冒號(hào)或分號(hào)前后加空格,如my_list = [1, 2, 3]。
函數(shù)參數(shù)間的逗號(hào)后加空格,如def my_function(a, b):。
注釋
使用井號(hào)(#)來(lái)標(biāo)記單行注釋,確保注釋簡(jiǎn)潔明了。
對(duì)于模塊、類、函數(shù)等,使用文檔字符串(docstrings)進(jìn)行注釋,遵循PEP 257規(guī)范。
異常處理
使用try...except...finally結(jié)構(gòu)處理異常,盡量捕獲具體異常而非使用泛型的except。
類與實(shí)例方法
實(shí)例方法的第一個(gè)參數(shù)應(yīng)為self,類方法的第一個(gè)參數(shù)為cls。
類的屬性和方法應(yīng)避免使用下劃線作為前綴或后綴(除非有特殊含義,如私有屬性)。
迭代
遍歷序列時(shí),優(yōu)先使用for...in循環(huán)而非索引訪問。
規(guī)范詳解
代碼布局
縮進(jìn)
每個(gè)縮進(jìn)級(jí)別使用4個(gè)空格
行延續(xù)時(shí),應(yīng)該括號(hào)、方括號(hào)、花括號(hào)在內(nèi)的隱式行折行元素垂直對(duì)齊,或者使用懸掛縮進(jìn)(懸掛縮進(jìn)是一種排版樣式,其中段落中除第一行外的所有行都要縮進(jìn)。在Python上下文中,帶括號(hào)語(yǔ)句的開始括號(hào)是該行的最后一個(gè)非空白字符,隨后的行被縮進(jìn)直到結(jié)束括號(hào))。
當(dāng)使用懸掛縮進(jìn)時(shí),需要注意以下幾點(diǎn):
第一行不應(yīng)包含任何參數(shù),并且應(yīng)該通過進(jìn)一步的縮進(jìn)來(lái)明確區(qū)分其為續(xù)行。
4個(gè)空格規(guī)則是可選的
# 正確示例:# 與開始分割符對(duì)齊.foo=long_function_name(var_one,var_two,var_three,var_four)# 增加4個(gè)空格 (額外的一層縮進(jìn)) 以區(qū)分參數(shù).deflong_function_name(var_one,var_two,var_three,var_four):print(var_one)# 懸掛縮進(jìn)應(yīng)該增加一個(gè)級(jí)別foo=long_function_name(var_one,var_two,var_three,var_four)# 懸掛縮進(jìn) 4個(gè)空格規(guī)則 是可選的.foo=long_function_name(var_one,var_two,var_three,var_four)# 錯(cuò)誤示例:# 非垂直對(duì)齊時(shí),第一行有參數(shù).foo=long_function_name(var_one,var_two,var_three,var_four)# 參數(shù)沒有進(jìn)一步縮進(jìn).deflong_function_name(var_one,var_two,var_three,var_four):print(var_one)
當(dāng)if語(yǔ)句的條件部分足夠長(zhǎng),需要寫在多行時(shí),值得注意的是,“if” 這個(gè)兩個(gè)字符的關(guān)鍵字加上一個(gè)空格和一個(gè)括號(hào),為后續(xù)的多行條件創(chuàng)建了一個(gè)自然的4個(gè)空格的縮進(jìn)。會(huì)與嵌套在if語(yǔ)句中的代碼產(chǎn)生視覺沖突。如何或是否進(jìn)一步在視覺上區(qū)分這樣的條件行與嵌套在if語(yǔ)句中的代碼。在這種情況下,沒有明確的規(guī)范,可接受的選項(xiàng)包括但不限于:
沒有額外的縮進(jìn)
添加一些注釋,可以提供在編譯器上的一些區(qū)分;支持語(yǔ)法高亮
在條件續(xù)行上,添加一些額外的縮進(jìn)
# 沒有額外的縮進(jìn)
if (this_is_one_thing and
? ? that_is_another_thing):
? ? do_something()
# 添加一些注釋,可以提供在編譯器上的一些區(qū)分,如支持語(yǔ)法高亮
if (this_is_one_thing and
? ? that_is_another_thing):
? ? # Since both conditions are true, we can frobnicate.
? ? do_something()
# 在條件續(xù)行上,添加一些額外的縮進(jìn)
if (this_is_one_thing
? ? ? ? and that_is_another_thing):
? ? do_something()
在多行結(jié)構(gòu)上的右括號(hào)、方括號(hào)、花括號(hào)可以排在列表最后一行的第一個(gè)非空白字符下,也可以排列在多行構(gòu)造的行的第一個(gè)字符下,如下所示:
# 列表最后一行的第一個(gè)非空白字符下
my_list = [
? ? 1, 2, 3,
? ? 4, 5, 6,
? ? ]
result = some_function_that_takes_arguments(
? ? 'a', 'b', 'c',
? ? 'd', 'e', 'f',
? ? )
# 多行構(gòu)造的行的第一個(gè)字符下
my_list = [
? ? 1, 2, 3,
? ? 4, 5, 6,
]
result = some_function_that_takes_arguments(
? ? 'a', 'b', 'c',
? ? 'd', 'e', 'f',
)
制表符還是空格?
空格是首選的縮進(jìn)方法。
制表符應(yīng)僅用于與已使用制表符縮進(jìn)的代碼保持一致。
Python不允許混合制表符和空格進(jìn)行縮進(jìn)。
行最大長(zhǎng)度限制
將所有行限制為最多79個(gè)字符。對(duì)于結(jié)構(gòu)限制較少的長(zhǎng)文本塊(文檔字符串或注釋),行長(zhǎng)應(yīng)限制為72個(gè)字符。
限制行長(zhǎng)后,當(dāng)使用代碼審查工具在相鄰列中顯示兩個(gè)版本的代碼,編輯器窗口寬度可以讓幾個(gè)文件并排打開,且顯示效果很好。
大多數(shù)工具中的默認(rèn)換行會(huì)破壞代碼的視覺結(jié)構(gòu),使其更難以理解。行長(zhǎng)限制是為了避免在窗口寬度設(shè)置為80的編輯器中換行,即使該工具在最后放置了一個(gè)標(biāo)記符號(hào),一些基于web的工具可能根本不提供動(dòng)態(tài)換行。
一些團(tuán)隊(duì)非常喜歡更長(zhǎng)的行。對(duì)于專門或主要由能夠就此問題達(dá)成一致的團(tuán)隊(duì)維護(hù)的代碼,可以將行長(zhǎng)限制增加到99個(gè)字符,前提是注釋和文檔字符串仍然以72個(gè)字符。
Python標(biāo)準(zhǔn)庫(kù)是保守的,要求將行限制為79個(gè)字符(文檔字符串/注釋限制為72個(gè)字符)。
換行的首選方法是在括號(hào)、方括號(hào)和花括號(hào)內(nèi)使用Python隱式行延續(xù)。將表達(dá)式括在括號(hào)中,可以將長(zhǎng)行分隔成多行。應(yīng)該優(yōu)先于使用反斜杠進(jìn)行行延續(xù)。
反斜杠有時(shí)可能仍然合適。例如,在Python 3.10之前長(zhǎng)的,多行的with-語(yǔ)句不能使用隱式行延續(xù),因此反斜杠在這種情況下是可以接受的。另一種情況是assert語(yǔ)句。
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())
應(yīng)該在二元運(yùn)算符之前還是之后換行?
幾十年來(lái),推薦的風(fēng)格是在二進(jìn)制運(yùn)算符之后中斷。但這可能會(huì)在兩個(gè)方面損害易讀性:運(yùn)算符往往分散在屏幕上的不同列中,每個(gè)運(yùn)算符都被從其操作數(shù)移開,移到前一行。在這里,眼睛必須做額外的工作來(lái)判斷哪些項(xiàng)目被添加,哪些項(xiàng)目被減去:
幾十年來(lái),推薦的風(fēng)格是在二進(jìn)制運(yùn)算符之后中斷。但是這會(huì)從兩個(gè)方面損害可讀性:
運(yùn)算符往往分散在屏幕上的不同列中,并且每個(gè)運(yùn)算符都從其操作數(shù)移到前一行。
眼睛必須做額外的工作來(lái)分辨哪些是加,哪些是減
# Wrong
# 運(yùn)算符離他們的操作數(shù)很遠(yuǎn)
income = (gross_wages +
? ? ? ? ? taxable_interest +
? ? ? ? ? (dividends - qualified_dividends) -
? ? ? ? ? ira_deduction -
? ? ? ? ? student_loan_interest)
為了解決這個(gè)易讀性問題,數(shù)學(xué)家和他們的出版商遵循相反的慣例。Donald Knuth在他的計(jì)算機(jī)和排版系列中解釋了傳統(tǒng)的規(guī)則:“雖然段落中的公式總是在二進(jìn)制運(yùn)算和關(guān)系之后中斷,但顯示的公式總是在二進(jìn)制運(yùn)算之前中斷“。
遵循數(shù)學(xué)傳統(tǒng)通常會(huì)使代碼更具可讀性:
# Correct:
# 很容易將操作符與操作數(shù)匹配
income = (gross_wages
? ? ? ? ? + taxable_interest
? ? ? ? ? + (dividends - qualified_dividends)
? ? ? ? ? - ira_deduction
? ? ? ? ? - student_loan_interest)
空白行
用兩個(gè)空行包圍頂級(jí)函數(shù)和類定義。
類內(nèi)的方法定義被一個(gè)空行包圍(前一個(gè)空行,后一個(gè)空行)。
可以(有節(jié)制地)使用額外的空行來(lái)分隔一組相關(guān)的函數(shù)。在一堆相關(guān)的單行代碼(例如,一組虛擬實(shí)現(xiàn))之間可以省略空白行。
在函數(shù)中盡量使用空行來(lái)表示邏輯部分。
Python接受control-L(即^L)換行符作為空白字符;許多工具將這些字符視為頁(yè)面分隔符,因此可以使用它們來(lái)分隔文件中相關(guān)部分的頁(yè)面。注意,一些編輯器和基于web的代碼查看器可能無(wú)法將control-L識(shí)別為換行符,因此會(huì)在其位置上顯示另一個(gè)字形。
源文件編碼
核心Python發(fā)行版中的代碼應(yīng)始終使用UTF-8,并且不應(yīng)具有編碼聲明。
在標(biāo)準(zhǔn)庫(kù)中,非UTF-8編碼應(yīng)僅用于測(cè)試目的。盡量避免使用非ASCII字符,最好僅用于表示地點(diǎn)和人名。如果使用非ASCII字符作為數(shù)據(jù),請(qǐng)避免使用嘈雜的Unicode字符。
Python標(biāo)準(zhǔn)庫(kù)中的所有標(biāo)識(shí)符都必須使用僅限ASCII的標(biāo)識(shí)符,并且應(yīng)該在可行的情況下使用英文單詞(在許多情況下,使用非英文的縮寫和技術(shù)術(shù)語(yǔ))。
imports導(dǎo)入
導(dǎo)入通常應(yīng)在單獨(dú)的行上
導(dǎo)入總是放在文件的頂部,僅在模塊注釋和文檔字符串之后,在模塊全局變量和常量之前。
導(dǎo)入應(yīng)按以下順序分組:
標(biāo)準(zhǔn)庫(kù)。
相關(guān)第三方庫(kù)。
本地應(yīng)用/庫(kù)。
應(yīng)該在每組導(dǎo)入之間放置一個(gè)空行
建議使用絕對(duì)導(dǎo)入;如果導(dǎo)入系統(tǒng)配置不正確(例如包中的目錄最終位于sys.path上),它們通常更具可讀性,而且往往表現(xiàn)得更好(或者至少給出更好的錯(cuò)誤消息)。然而,顯式相對(duì)導(dǎo)入是絕對(duì)導(dǎo)入的一種可接受的替代方案,特別是在處理復(fù)雜的包布局時(shí),使用絕對(duì)導(dǎo)入會(huì)有不必要地冗長(zhǎng)。標(biāo)準(zhǔn)庫(kù)應(yīng)避免使用復(fù)雜的包布局,并始終使用絕對(duì)導(dǎo)入。
應(yīng)該避免通配符導(dǎo)入(from import *),它們會(huì)使名稱空間中出現(xiàn)的名稱變得不清楚,從而混淆讀者和許多自動(dòng)化工具。通配符導(dǎo)入有一個(gè)合理的用例,它是將內(nèi)部接口作為公共API的一部分重新發(fā)布(例如,用可選加速器模塊的定義覆蓋接口的純Python實(shí)現(xiàn),并且確切地說(shuō),哪些定義將被覆蓋是未知的)
# Correct:
import os
import sys
# Wrong:
import sys, os
# Correct:
from subprocess import Popen, PIPE
# 絕對(duì)導(dǎo)入
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
# 相對(duì)導(dǎo)入
from . import sibling
from .sibling import example
# 從包含類的模塊導(dǎo)入類時(shí),沒有命名沖突時(shí),可以這樣拼寫
from myclass import MyClass
from foo.bar.yourclass import YourClass
# 從包含類的模塊導(dǎo)入類時(shí),命名沖突時(shí),可以這樣拼寫
import myclass
import foo.bar.yourclass
模塊級(jí)"Dunder"名稱
模塊級(jí)“dunders”(具有兩個(gè)前導(dǎo)和兩個(gè)尾隨的名稱下劃線)如__all__、__author__、__version__等,應(yīng)放在模塊文檔字符串之后,但在除 from __future__ imports 之外的任何導(dǎo)入語(yǔ)句之前。Python強(qiáng)制要求 future-imports 必須出現(xiàn)在模塊中除文檔字符串之外的任何其他代碼之前
"""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
字符串引號(hào)
在Python中,單引號(hào)字符串和雙引號(hào)字符串是一樣的。這個(gè)PEP對(duì)此不做推薦。選擇一個(gè)規(guī)則并堅(jiān)持下去。但是,當(dāng)一個(gè)字符串包含單引號(hào)或雙引號(hào)字符時(shí),請(qǐng)使用另一個(gè)字符以避免字符串中出現(xiàn)反斜杠。它提高了可讀性。
表達(dá)式和語(yǔ)句中的空格
在以下情況下避免無(wú)關(guān)的空格
緊接在括號(hào)、括號(hào)或大括號(hào)內(nèi)
尾隨的逗號(hào)和后面的右括號(hào)之間
緊接在逗號(hào)、分號(hào)或冒號(hào)之前的
在切片中,冒號(hào)的作用類似于二進(jìn)制操作符,并且在兩邊的應(yīng)該相等(將其視為具有最低優(yōu)先級(jí)的操作符)。在擴(kuò)展切片中,兩個(gè)冒號(hào)必須具有相同的間距。例外:省略切片參數(shù)時(shí),省略空格
緊接在開始函數(shù)調(diào)用的參數(shù)列表的左圓括號(hào)之前
緊接在開始索引或切片的左括號(hào)之前
賦值(或其他)操作符周圍有多個(gè)空格,以便與另一個(gè)操作符對(duì)齊
# 緊接在括號(hào)、括號(hào)或大括號(hào)內(nèi)
# Correct:
spam(ham[1], {eggs: 2})
# Wrong:
spam( ham[ 1 ], { eggs: 2 } )
# 尾隨的逗號(hào)和后面的右括號(hào)之間
# Correct:
foo = (0,)
# Wrong:
bar = (0, )
# 緊接在逗號(hào)、分號(hào)或冒號(hào)之前的
# Correct:
if x == 4: print(x, y); x, y = y, x
# Wrong:
if x == 4 : print(x , y) ; x , y = y , x
# 切片中
# 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]
# 函數(shù)調(diào)用
# Correct:
spam(1)
# Wrong:
spam (1)
# 開始索引或切片的左括號(hào)之前
# Correct:
dct['key'] = lst[index]
# Wrong:
dct ['key'] = lst [index]
# 賦值(或其他)操作符周圍有多個(gè)空格
# Correct:
x = 1
y = 2
long_variable = 3
# Wrong:
x? ? ? ? ? ? = 1
y? ? ? ? ? ? = 2
long_variable = 3
其他建議
避免在任何地方留下空格。因?yàn)樗ǔJ遣豢梢姷模赡軙?huì)令人困惑:例如,反斜杠后面跟著空格和換行符不算作行繼續(xù)標(biāo)記。一些編輯器不保留它,許多項(xiàng)目(如CPython本身)有拒絕它的預(yù)提交鉤子。
總是在這些二元操作符的兩邊分別用一個(gè)空格括起來(lái):賦值(=)、增廣賦值(+=、-=等)、比較(==、<、>、!=、<>、<=、>=、in、not in、is、is not)、布爾值(and、or、not)。
如果使用具有不同優(yōu)先級(jí)的操作符,請(qǐng)考慮在具有最低優(yōu)先級(jí)的操作符周圍添加空白。用你自己的判斷;但是,永遠(yuǎn)不要使用多個(gè)空格,并且在二進(jìn)制操作符的兩側(cè)始終具有相同數(shù)量的空白。
# 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)
函數(shù)注釋應(yīng)該使用冒號(hào)的常規(guī)規(guī)則,并且如果存在->箭頭周圍總是有空格。
# Correct:
def munge(input: AnyStr): ...
def munge() -> PosInt: ...
# Wrong:
def munge(input:AnyStr): ...
def munge()->PosInt: ...
當(dāng)用于指示關(guān)鍵字參數(shù)時(shí),或者用于指示未標(biāo)注的函數(shù)形參的默認(rèn)值時(shí),不要在=號(hào)周圍使用空格
# 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)
# 當(dāng)將參數(shù)注釋與默認(rèn)值結(jié)合使用時(shí),一定要在 = 號(hào)周圍使用空格
# 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): ...
雖然有時(shí)將if/for/ While語(yǔ)句與一個(gè)小體放在同一行是可以的,但對(duì)于多子句語(yǔ)句永遠(yuǎn)不要這樣做。也不要折疊這么長(zhǎng)的線。
# 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()
何時(shí)使用尾隨逗號(hào)
尾隨逗號(hào)通常是可選的,除非在制作一個(gè)元素的元組時(shí)它們是強(qiáng)制性的。為清楚起見,建議將后者括在(技術(shù)上多余的)括號(hào)中
# Correct:
FILES = ('setup.cfg',)
# Wrong:
FILES = 'setup.cfg',
當(dāng)尾隨逗號(hào)是多余的,當(dāng)使用版本控制系統(tǒng)時(shí),當(dāng)期望值、參數(shù)或?qū)腠?xiàng)的列表隨著時(shí)間的推移而擴(kuò)展時(shí),它們通常是有用的。該模式是將每個(gè)值(等)單獨(dú)放在一行上,總是在后面添加逗號(hào),并在下一行添加右括號(hào)/大括號(hào)/大括號(hào)。然而,在結(jié)束分隔符的同一行上有一個(gè)尾隨逗號(hào)是沒有意義的(除了上面的單例元組)。
# Correct:
FILES = [
? ? 'setup.cfg',
? ? 'tox.ini',
? ? ]
initialize(FILES,
? ? ? ? ? error=True,
? ? ? ? ? )
# Wrong:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)
注釋
與代碼相矛盾的注釋比沒有注釋更糟糕。當(dāng)代碼更改時(shí),始終優(yōu)先考慮保持更新注釋!
注釋應(yīng)該是完整的句子。第一個(gè)單詞應(yīng)該大寫,除非它是以小寫字母開頭的標(biāo)識(shí)符(永遠(yuǎn)不要改變標(biāo)識(shí)符的大小寫!)
塊注釋通常由一個(gè)或多個(gè)由完整句子組成的段落組成,每個(gè)句子以句號(hào)結(jié)尾。
在多句注釋中,除了最后一句之外,你應(yīng)該在句子結(jié)束后使用一到兩個(gè)空格
確保你的評(píng)論是清晰的,很容易被其他的,你寫作的使用語(yǔ)言的人理解。
來(lái)自非英語(yǔ)國(guó)家的Python程序員:請(qǐng)用英語(yǔ)寫你的注釋,除非你120%確定代碼永遠(yuǎn)不會(huì)被不講你的語(yǔ)言的人閱讀。
塊注釋
塊注釋通常適用于部分(或全部)代碼之后,并縮進(jìn)到與該代碼相同的級(jí)別。每一行塊注釋以#和單個(gè)空格開頭(除非是評(píng)論中的縮進(jìn)文本)。
塊注釋中的段落由包含單個(gè)#的行分隔。
行內(nèi)注釋
謹(jǐn)慎使用行內(nèi)注釋。
行內(nèi)注釋是與語(yǔ)句在同一行上的注釋。行內(nèi)注釋應(yīng)與語(yǔ)句至少用兩個(gè)空格分隔。它們應(yīng)該以#和單個(gè)空格開頭。
行內(nèi)注釋是不必要的,如果它們陳述了顯而易見的內(nèi)容,實(shí)際上會(huì)分散注意力。不要這樣做:
x = x + 1? ? ? ? ? ? ? ? # Increment x,不要這樣做
x = x + 1? ? ? ? ? ? ? ? # Compensate for border, 這種情況下是有用的
文檔字符串
編寫好的文檔字符串(又名“docstrings”)的約定在PEP 257中得到了不朽的體現(xiàn)。
為所有公共模塊、函數(shù)、類和方法編寫文檔字符串。文檔字符串對(duì)于非公共方法不是必需的,但應(yīng)該有一個(gè)注釋來(lái)描述該方法的功能。這個(gè)注釋應(yīng)該出現(xiàn)在 def 之后。
PEP 257描述了良好的文檔字符串約定。注意,最重要的是,多行文檔字符串結(jié)尾的"""應(yīng)該單獨(dú)在一行上
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
對(duì)于單行文檔字符串,請(qǐng)將結(jié)束的"""保持在同一行
"""Return an ex-parrot."""
命名約定
Python庫(kù)的命名約定有點(diǎn)混亂,所以我們永遠(yuǎn)不會(huì)完全一致——盡管如此,這里是目前推薦的命名標(biāo)準(zhǔn)。新的模塊和包(包括第三方框架)應(yīng)該按照這些標(biāo)準(zhǔn)編寫,但是如果現(xiàn)有庫(kù)有不同的風(fēng)格,內(nèi)部一致性是首選。
最重要的原則
作為API的公共部分對(duì)用戶可見的名稱應(yīng)遵循反映用法而不是實(shí)現(xiàn)的約定。
描述性:命名風(fēng)格
有許多不同的命名風(fēng)格。它有助于識(shí)別正在使用的命名風(fēng)格,而不是它們的用途。
通常區(qū)分以下命名風(fēng)格:
b(單小寫字母)
B(單大寫字母)
lowercase
lower_case_with_underscores
UPPERCASE
UPPER_CASE_WITH_UNDERSCORES
CapitalizedWords(或CapWords,或CamelCase——因其字母凹凸不平而得名)。這有時(shí)也被稱為StudlyCaps,首字母大寫
注意:在CapWords中使用首字母縮略詞時(shí),請(qǐng)將首字母縮略詞的所有字母大寫。因此HTTPServerError優(yōu)于HttpServerError。
mixedCase(與CapitalizedWords不同的是初始小寫字符)
Capitalized_Words_With_Underscores(不推薦)
還有一種風(fēng)格是使用一個(gè)簡(jiǎn)短的唯一前綴將相關(guān)的名稱組合在一起。這在Python中使用得并不多,但為了完整性而提到它。例如,os.stat()函數(shù)返回一個(gè)元組,其項(xiàng)通常具有st_mode、st_size、st_mtime等名稱。(這樣做是為了強(qiáng)調(diào)與POSIX系統(tǒng)調(diào)用結(jié)構(gòu)的字段的對(duì)應(yīng)關(guān)系,這有助于程序員熟悉它。)
X11庫(kù)的所有公共函數(shù)都使用前導(dǎo)X。在Python中,這種風(fēng)格通常被認(rèn)為是不必要的,因?yàn)閷傩院头椒那熬Y是對(duì)象,函數(shù)名的前綴是模塊名
此外,還可以識(shí)別使用前導(dǎo)或尾隨下劃線的特殊形式:
_single_leading_underscore弱的“內(nèi)部使用”指示。例如,from M import *不導(dǎo)入名稱以下劃線開頭的對(duì)象。
single_trailing_underscore_:按照約定使用,以避免與Python關(guān)鍵字沖突,例如:
tkinter.Toplevel(master, class_='ClassName')
__double_leading_underscore:命名類屬性時(shí),調(diào)用名稱修飾(類內(nèi)FooBar,__boo變成_FooBar__boo)。
__double_leading_and_trailing_underscore__:存在于用戶控制的命名空間中的“魔法”對(duì)象或?qū)傩?。例如__init__, __import__或__file__。永遠(yuǎn)不要編造這樣的名字;只按照文檔的要求使用它們
規(guī)范性:命名約定
要避免的名稱
不要使用字符“l(fā)”(小寫字母el)、“O”(大寫字母oh)或“I”(大寫字母eye)作為單字符變量名稱。
在某些字體中,這些字符與數(shù)字1和0無(wú)法區(qū)分。當(dāng)想使用“l(fā)”時(shí),請(qǐng)改用“L”。
ASCII兼容性
標(biāo)準(zhǔn)庫(kù)中使用的標(biāo)識(shí)符必須與ASCII兼容,如PEP 3131的策略部分所述
包和模塊名稱
模塊應(yīng)該有簡(jiǎn)短,全小寫名稱。如果可以提高易讀性,可以在模塊名稱中使用下劃線。Python包也應(yīng)該有簡(jiǎn)短的全小寫名稱,盡管不鼓勵(lì)使用下劃線。
用C或C++編寫的擴(kuò)展模塊提供更高級(jí)別(例如更面向?qū)ο螅┑腜ython模塊界面,C/C++模塊有一個(gè)前導(dǎo)下劃線(例如_socket)。
類名稱
類名通常應(yīng)使用CapWords約定。
在接口被記錄,并主要用作可調(diào)用對(duì)象的情況下,可以使用函數(shù)的命名約定。
請(qǐng)注意,內(nèi)置名稱有一個(gè)單獨(dú)的約定:大多數(shù)內(nèi)置名稱是單個(gè)單詞(或兩個(gè)單詞一起運(yùn)行),CapWords約定僅用于異常名稱和內(nèi)置常量。
類型變量名
在PEP 484中引入的類型變量的名稱通常應(yīng)該使用CapWords,更傾向于短名稱:T、AnyStr、Num。建議添加用于聲明協(xié)變變量的變量的后綴_co或_contra或相應(yīng)的逆變行為:
from typing import TypeVar
VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)
異常名稱
異常應(yīng)該是類,所以這里適用類命名約定。但應(yīng)該在異常名稱上使用后綴“Error”(如果異常實(shí)際上是錯(cuò)誤)。
全局變量名
(希望這些變量?jī)H用于一個(gè)模塊內(nèi)。)約定與函數(shù)的約定大致相同。
設(shè)計(jì)為通過from M import * 使用的模塊應(yīng)該使用__all__機(jī)制來(lái)防止導(dǎo)出全局變量,或者使用較舊的約定,即用下劃線為這些全局變量前綴(你可能想要表明這些全局變量是“module non-public”)。
函數(shù)和變量名
函數(shù)名應(yīng)該是小寫的,必要時(shí)用下劃線分隔,以提高可讀性。
變量名遵循與函數(shù)名相同的約定。
mixedCase只允許在已經(jīng)是流行風(fēng)格的上下文中使用(例如threading.py),以保持向后兼容性。
函數(shù)和方法參數(shù)
始終使用self作為實(shí)例方法的第一個(gè)參數(shù)。
始終使用cls作為類方法(通過@classmethod 裝飾器標(biāo)記的方法)的第一個(gè)參數(shù)。
如果函數(shù)參數(shù)的名稱與保留關(guān)鍵字沖突,通常最好在后面附加一個(gè)下劃線,而不是使用縮寫或拼寫錯(cuò)誤。因此class_比class好。(也許最好是使用同義詞來(lái)避免這種沖突。)
方法名稱和實(shí)例變量
使用函數(shù)命名規(guī)則:必要時(shí)小寫,單詞用下劃線分隔,以提高易讀性。
僅對(duì)非公共方法和實(shí)例變量使用一個(gè)前導(dǎo)下劃線。
為避免與子類發(fā)生名稱沖突,請(qǐng)使用兩個(gè)前導(dǎo)下劃線來(lái)調(diào)用Python的名稱混淆規(guī)則。
Python用類名混淆了這些名稱:如果類Foo有一個(gè)名為__a的屬性,它不能被Foo.__a訪問。(固執(zhí)用戶仍然可以通過調(diào)用Foo._Foo__a來(lái)獲得訪問權(quán)。)通常,雙前導(dǎo)下劃線應(yīng)該僅用于避免與設(shè)計(jì)為子類化的類中的屬性發(fā)生名稱沖突。
注意:關(guān)于__names的使用有一些爭(zhēng)議。
常量
常量通常在模塊級(jí)別定義,并以大寫字母書寫,并用下劃線分隔單詞。例如MAX_OVERFLOW和TOTAL。
繼承設(shè)計(jì)
始終決定一個(gè)類的方法和實(shí)例變量(統(tǒng)稱為“屬性”)應(yīng)該是公共的還是非公共的。如果有疑問,請(qǐng)選擇非公共;稍后將其公開比將公共屬性非公開更容易。
公共屬性是希望類中不相關(guān)的客戶端使用的屬性,并承諾避免向后不兼容的更改。非公共屬性是那些不打算被第三方使用的屬性;不能保證非公共屬性不會(huì)更改甚至被刪除。
我們?cè)谶@里沒有使用術(shù)語(yǔ)“私有”,因?yàn)樵赑ython中沒有屬性是真正私有的(通常沒有不必要的工作量)。
另一類屬性是“子類API”的一部分(在其他語(yǔ)言中通常稱為“protected”)。有些類被設(shè)計(jì)為可以繼承,以擴(kuò)展或修改類行為的某些方面。在設(shè)計(jì)這樣的類時(shí),要注意明確地決定哪些屬性是公共的,哪些屬性是子類API的一部分,哪些屬性只能由基類使用。
以下是python繼承設(shè)計(jì)指南:
公共屬性不應(yīng)有前導(dǎo)下劃線。
如果公共屬性名與保留關(guān)鍵字沖突,請(qǐng)?jiān)趯傩悦竺嫣砑右粋€(gè)下劃線。這比縮寫或錯(cuò)誤的拼寫更可取。(然而,盡管有此規(guī)則,' cls '是已知為類的任何變量或參數(shù)的首選拼寫,特別是類方法的第一個(gè)參數(shù)。)
對(duì)于簡(jiǎn)單的公共數(shù)據(jù)屬性,最好只公開屬性名稱,不使用復(fù)雜的訪問器/mutator方法。請(qǐng)記住,如果您發(fā)現(xiàn)一個(gè)簡(jiǎn)單的數(shù)據(jù)屬性需要發(fā)展函數(shù)行為,那么Python為將來(lái)的增強(qiáng)提供了一條簡(jiǎn)單的途徑。在這種情況下,使用屬性將功能實(shí)現(xiàn)隱藏在簡(jiǎn)單的數(shù)據(jù)屬性訪問語(yǔ)法之后
如果您的類打算被子類化,并且您有不希望子類使用的屬性,請(qǐng)考慮使用雙下劃線前導(dǎo)而不帶下劃線來(lái)命名它們。這調(diào)用了Python的名稱混淆算法,其中類的名稱被混淆為屬性名稱。如果子類無(wú)意中包含具有相同名稱的屬性,這有助于避免屬性名稱沖突
公共和內(nèi)部接口
任何向后兼容性保證只適用于公共接口。因此,重要的是用戶能夠清楚地區(qū)分公共接口和內(nèi)部接口。
文檔化的接口被認(rèn)為是公共的,除非文檔顯式地聲明它們是臨時(shí)的或免除通常的向后兼容性保證的內(nèi)部接口。所有未歸檔的接口都應(yīng)該假定是內(nèi)部的。
為了更好地支持自省,模塊應(yīng)該使用__all__屬性在其公共API中顯式聲明名稱。將__all__設(shè)置為空列表表明該模塊沒有公共API
即使正確設(shè)置了__all__,內(nèi)部接口(包、模塊、類、函數(shù)、屬性或其他名稱)仍應(yīng)以單個(gè)下劃線作為前綴。
如果任何包含名稱空間(包、模塊或類)被認(rèn)為是內(nèi)部的,那么接口也被認(rèn)為是內(nèi)部的。
導(dǎo)入的名稱應(yīng)始終被視為實(shí)現(xiàn)細(xì)節(jié)。其他模塊不能依賴于對(duì)這些導(dǎo)入名稱的間接訪問,除非它們是包含模塊API(如os)的顯式文檔部分。路徑或包的__init__模塊,該模塊公開子模塊的功能。
編程建議
代碼的編寫方式不應(yīng)損害Python的其他實(shí)現(xiàn)(PyPy、Jython、IronPython、Cython、Psyco等)。
例如,對(duì)于形式為a += b或a = a + b的語(yǔ)句,不要依賴CPython,對(duì)就地字符串連接的有效實(shí)現(xiàn)。即使在CPython中,這種優(yōu)化也很脆弱(它僅適用于某些類型),并且在不使用refcounting的實(shí)現(xiàn)中根本不存在。在庫(kù)中對(duì)性能敏感的部分,應(yīng)該使用" .join()形式。這將確??绺鞣N實(shí)現(xiàn)的連接在線性時(shí)間內(nèi)發(fā)生。
與None之類的單例比較應(yīng)該總是使用is或is not,而不是相等操作符。
另外,當(dāng)你真正想表達(dá) if x is not None 時(shí),要注意 if x 這種表達(dá)式,例如,在測(cè)試一個(gè)變量或參數(shù)是默認(rèn)None 還是被設(shè)置為其他值。其他值可能有一個(gè)類型(如容器),那么這個(gè)bool表達(dá)式可能就是 false。
使用是not 操作符而不是not…is。雖然這兩個(gè)表達(dá)式在功能上是相同的,但前者更具可讀性,是首選
# Correct:
if foo is not None:
# Wrong:
if not foo is None:
在實(shí)現(xiàn)多種比較排序操作時(shí),最好實(shí)現(xiàn)所有六個(gè)操作(__eq__, __ne__, __lt__, __le__, __gt__, __ge__),而不是只依賴于代碼只執(zhí)行特定的比較。
為了盡量減少工作量,functools.total_ordering()裝飾器提供了一個(gè)工具來(lái)生成缺失的比較方法。
PEP 207指出Python假定了自反性規(guī)則。因此,解釋器可以將y > x與x < y交換,y >= x與x <= y交換,并且可以交換x == y和x != y的參數(shù)。sort()和min()操作保證使用<操作符,而max()函數(shù)使用>操作符。但是,最好實(shí)現(xiàn)所有六個(gè)操作,這樣在其他上下文中就不會(huì)出現(xiàn)混淆。
始終使用def語(yǔ)句,而不是將lambda表達(dá)式直接綁定到標(biāo)識(shí)符的賦值語(yǔ)句
# Correct:
def f(x): return 2*x
# Wrong:
f = lambda x: 2*x
第一種形式意味著結(jié)果函數(shù)對(duì)象的名稱特別為' f '而不是通用的' <lambda> '。一般來(lái)說(shuō),這對(duì)于回溯和字符串表示更有用。賦值語(yǔ)句的使用消除了lambda表達(dá)式相對(duì)于顯式def語(yǔ)句所能提供的唯一好處(即它可以嵌入到更大的表達(dá)式中)。
異常類從Exception,而不是BaseException繼承。從BaseException直接繼承是為了捕獲哪些幾乎總是錯(cuò)誤的異常保留的
基于代碼捕獲異常區(qū)別來(lái)設(shè)計(jì)異常層次結(jié)構(gòu),可能是必須的,而不是基于引發(fā)異常的位置。目的是回答這個(gè)問題“哪里出了問題?”,而不是僅僅指出“發(fā)生了問題”(參見PEP 3151,獲取有關(guān)內(nèi)建異常層次結(jié)構(gòu)的示例)。
這里適用于類命名約定,但如果異常是錯(cuò)誤,則應(yīng)該在異常類后面加上后綴“Error”。用于非本地流程控制或其他形式的信令非錯(cuò)誤異常不需要特殊后綴。
適當(dāng)?shù)厥褂卯惓f湣?yīng)該使用raise X from Y來(lái)指示顯式替換,而不是丟失原始的回溯。
當(dāng)故意替換內(nèi)部異常時(shí)(使用raise X from None),確保相關(guān)細(xì)節(jié)被轉(zhuǎn)移到新異常(例如在將KeyError轉(zhuǎn)換為AttributeError時(shí)保留屬性名稱,或在新異常消息中嵌入原始異常的文本)。
當(dāng)捕獲異常時(shí),盡可能地提及特定的異常,而不是僅僅使用except:子句
try:
? ? import platform_specific_module
except ImportError:
? ? platform_specific_module = None
僅使用except:子句將捕獲SystemExit和KeyboardInterrupt異常,使得用Control-C中斷程序變得更加困難,并且可以掩蓋其他問題。如果你想捕獲程序所有的異常,使用except Exception(僅使用 except等價(jià)于 except BaseException)。
一個(gè)好的經(jīng)驗(yàn)法則是將 “except” 子句的使用,限制在以下兩種情況:
如果異常處理程序?qū)⒋蛴』蛴涗浕厮?;至少用戶?huì)意識(shí)到發(fā)生了錯(cuò)誤。
如果代碼需要做一些清理工作,但隨后讓異常通過raise向上傳播。try...finally可以更好地處理這種情況。
在捕獲操作系統(tǒng)錯(cuò)誤時(shí),請(qǐng)選擇Python 3.3中引入的顯式異常層次結(jié)構(gòu),而不是自省errno值。
對(duì)于所有try/except子句,需要將 try 語(yǔ)句的代碼量限制到最小,這樣能避免掩蓋bug。
# 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)
當(dāng)特定代碼部分是本地資源時(shí),使用with聲明,以確保使用后及時(shí)可靠地清理。try/finally語(yǔ)句也是可以接受的。
上下文管理器在執(zhí)行除獲取和釋放資源以外的其他操作時(shí),應(yīng)該通過單獨(dú)的函數(shù)或方法調(diào)用它們。
# Correct:
with conn.begin_transaction():
? ? do_stuff_in_transaction(conn)
# Wrong:
with conn:
? ? do_stuff_in_transaction(conn)
后一個(gè)例子沒有提供任何信息來(lái)表明__enter__和__exit__方法,除了在事務(wù)結(jié)束后關(guān)閉連接之外正在做其他事情。在這種情況下,明確是很重要的。
在返回語(yǔ)句中保持一致。函數(shù)中的所有返回語(yǔ)句都應(yīng)該返回一個(gè)表達(dá)式,或者都不應(yīng)該返回。如果任何return語(yǔ)句返回一個(gè)表達(dá)式,任何沒有返回值的return語(yǔ)句都應(yīng)該顯式地將其聲明為return None,并且應(yīng)該在函數(shù)末尾顯式的加上return語(yǔ)句。
# 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)
Use ''.startswith() and ''.endswith() 代替分片檢查前綴或后綴
startswith()和endswith()更干凈,更不容易出錯(cuò):
# Correct:
if foo.startswith('bar'):
# Wrong:
if foo[:3] == 'bar':
對(duì)象類型比較應(yīng)該始終使用isinstance(),而不是直接比較類型
# Correct:
if isinstance(obj, int):
# Wrong:
if type(obj) is type(1):
對(duì)于序列(字符串、列表、元組),使用空序列為假的事實(shí)
# Correct:
if not seq:
if seq:
# Wrong:
if len(seq):
if not len(seq):
不要編寫依賴重要末尾空白的字符串文字。這樣的尾隨空格在視覺上是無(wú)法區(qū)分的,一些編輯器(或者最近的reindent.py)會(huì)修剪它們
不要使用 == 將布爾值 與True或False進(jìn)行比較
# Correct:
if greeting:
# Wrong:
if greeting == True:
# Worse 更糟
if greeting is True:
使用流控制語(yǔ)句,return/break/continue 應(yīng)在try…finally內(nèi),不鼓勵(lì)流控制語(yǔ)句跳轉(zhuǎn)到finally之外。這是因?yàn)檫@樣的語(yǔ)句將隱式地取消了任何活動(dòng)在finally內(nèi)部的異常。
# Wrong:
def foo():
? ? try:
? ? ? ? 1 / 0
? ? finally:
? ? ? ? return 42
函數(shù)注釋
隨著PEP 484的接受,函數(shù)注釋的樣式也發(fā)生了改變。
函數(shù)注釋應(yīng)使用PEP 484語(yǔ)法
不再鼓勵(lì)使用本PEP中先前推薦的注釋樣式進(jìn)行實(shí)驗(yàn)。
然而,在stdlib之外,現(xiàn)在鼓勵(lì)在PEP 484規(guī)則范圍內(nèi)進(jìn)行實(shí)驗(yàn)。例如,用PEP 484風(fēng)格的類型注釋標(biāo)記大型第三方庫(kù)或應(yīng)用程序,檢查添加這些注釋的容易程度,并觀察它們的存在是否增加了代碼的可理解性。
Python標(biāo)準(zhǔn)庫(kù)在采用這種注釋方面應(yīng)該是保守的,但是對(duì)于新代碼和大型重構(gòu),它們的使用是允許的。。
對(duì)于想要以不同的方式使用函數(shù)注釋的代碼,建議使用這種形式的注釋。
# type: ignore
靠近文件頂部的;這告訴類型檢查器忽略所有注釋。(可以在PEP 484中找到禁用類型檢查器投訴的更細(xì)粒度的方法
與檢查器一樣,類型檢查器也是可選的獨(dú)立工具。默認(rèn)情況下,Python解釋器不應(yīng)該因?yàn)轭愋蜋z查而發(fā)出任何消息,也不應(yīng)該根據(jù)注釋改變它們的行為
不想使用類型檢查器的可以自由地忽略它們。然而,第三方庫(kù)包可能希望在這些包上運(yùn)行類型檢查器。為此,PEP 484建議使用由類型檢查器讀取的存根文件:.pyi文件,而不是相應(yīng)的.py文件。存根文件可以與庫(kù)一起發(fā)布,也可以通過typeshed repo單獨(dú)發(fā)布(在庫(kù)作者允許的情況下)。
變量注釋
PEP 526引入了變量標(biāo)注。它們的樣式建議類似于上面描述的函數(shù)注釋:
模塊級(jí)變量、類和實(shí)例變量以及局部變量的注釋應(yīng)該在冒號(hào)后面有一個(gè)空格。
冒號(hào)前不應(yīng)該有空格。
如果賦值項(xiàng)有右手邊,那么等式兩邊應(yīng)該正好有一個(gè)空格
# 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
盡管Python 3.6接受PEP 526,但變量注釋語(yǔ)法是所有Python版本上存根文件的首選語(yǔ)法(詳細(xì)信息請(qǐng)參閱PEP 484)。