Python編碼規範(PEP8)
Python編碼規範(PEP8)
Written by xiaoQQya on 2020/08/26.目錄
PEP
是Python Enhancement Proposal
的縮寫,翻譯過來就是Python
增強建議書。
一.程式碼佈局
1.1 縮排
每個縮排層次為4個空格。
連續行包裝元素的兩種方案:
- 隱式續行:續行與圓括號、方括號或花括號的左括號對齊;
- 懸掛縮排:首行沒有引數,續行應該多縮排一級以便和正常的縮排區分。
# 隱式續行 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個空格以外的其他空格 foo = long_function_name( var_one, var_two, var_three, var_four)
1.2 Tab 或 空格
-
空格是首選的縮排方法。
-
唯一的一種情況下建議使用Tab:之前的程式碼已經使用了Tab,為了保持一致性。
注:
Python3
不允許混合使用Tab鍵和空格進行縮排。
1.3 單行最大長度
-
單行最大長度不超過79個字元。
-
為了使較長的文字塊具有較少的結構限制(文件字串或者註釋),單行最大長度應該限制為72個字元。
-
換行的首選方法是使用
Python
的隱含續行機制,即在小括號、中括號、大括號內部自動續行。很長的行可以通過把表示式包含在圓括號內,實現自動續行,相比使用反斜槓\續行,這個方法應該更為推薦。 -
有時候反斜槓仍然是適合的。例如,with語句不能使用隱式續行,因此可以接受反斜槓。
1.4 二元操作符換行
在二元操作符之前和之後換行兩種方式都是被允許的,但是從可讀性的角度,建議在二元操作符之前換行。
-
運算子趨向於分散在螢幕上的不同列上,並且每個運算子都從其運算元移至上一行。但是,這降低了程式碼的可讀性:
# 二元操作符之後換行 income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest)
-
遵循數學的傳統通常會導致程式碼更具可讀性:
# 二元操作符之前換行 income = (gross_wages + taxable_interest + (dividends - qualified_dividends) - ira_deduction - student_loan_interest)
注:在
Python
程式碼中,只要約定在本地是一致的,就可以在二元操作符之前或者之後換行。
1.5 空行
- 頂層方法或者類定義使用兩個空行;
- 類中的方法定義使用單個空行;
- 可以使用額外的空行(儘量少)來分隔一組相關的函式;
- 在一些列相關的僅佔一行的函式之間,空行可以被省略(比如一組虛擬函式定義);
- 在方法內部,使用空行來表示邏輯區域(儘量少)。
1.6 原始檔編碼
-
Python
核心發行版中的原始碼應該一直使用utf-8
編碼(Python2
中使用ASCII
編碼)。 -
使用
ASCII
編碼(Python2
)或者utf-8
編碼(Python3
)的檔案不應該新增編碼宣告。
1.7 模組引入
-
匯入通常應該在單獨的行上,但是從單個模組中匯入多個部分是可以寫在一行上:
# 正確的寫法 import os import sys # 錯誤的寫法 import os, sys # 單個模組匯入多個部分 from subprocess import Popen, PIPE
-
匯入模組應該放在檔案頂部,在模組註釋和文件字串之後,在模組全域性變數和常量之前。
-
匯入應按以下順序分組:
- 標準庫匯入;
- 相關的第三方庫匯入;
- 本地庫或者方法匯入。
注:在每組匯入之間應該放置一個空行。
-
推薦使用絕對匯入,因為如果匯入系統配置不正確,則它們通常更具可讀性,並且通常表現更好。
# 絕對匯入 import mypkg.sibling from mypkg import sibling from mypkg.sibling import example
-
顯示相對匯入是絕對匯入的一種可接受的替代方法,尤其是在處理複雜的包裝佈局時,絕對匯入顯得冗餘的情況下。
# 顯示相對匯入 from . import sibling from .sibling import example
-
隱式相對匯入應該禁止使用,在
Python3
中已經被去掉了。# 隱式相對匯入 import sibling
-
從包含類的模組中匯入類。
# 一般情況 from myclass import MyClass from foo.bar.yourclass import YourClass # 如果和本地名稱衝突,則應該明確拼寫它們 import myclass import foo.bar.yourclass
-
應該避免使用萬用字元匯入。
# 萬用字元匯入 from module import *
1.8 模組級雙下劃線命名
模組級別雙下劃線變數比如__all__
,__author__
應該放在模組文件字串之後,但是需要放在import
語句之前。存在一個例外,from __future__ import
應該放在雙下劃線變數之前,Python
要求模組中__future__
的匯入必須出現在除文件字串之外的任何其他程式碼之前。程式碼如下所示:
# 文件字串,放在最前面
"""
This is the example module.
This module does stuff.
"""
# 特殊情況,__future__匯入必須放在除了文件字串之後的所有程式碼前面
from __future__ import barry_as_FLUFL
# 雙下劃線變數,必須放在import之前
__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'
# import 語句
import os
import sys
二.字串引號
在Python
中表示字串時,不管是單引號還是雙引號都是一樣的。但是不推薦將這兩種方式看作一樣並且混用,最好選擇一種規則並堅持使用。當字串中包含單引號時,採用雙引號來表示字串,反之也一樣,這樣可以避免使用反斜槓,程式碼也更易讀。
對於三引號標識的字串,使用雙引號字串來表示,這樣可以和PEP 257的文件字串規則保持一致。
三.表示式和語句中的空格
3.1 需要注意的地方
在下列情形中避免使用過多的空白:
-
方括號、圓括號和花括號之後。
# 正確的例子 spam(ham[1], {eggs: 2}) # 錯誤的例子 spam( ham[ 1 ], { eggs: 2 } )
-
逗號、分號和冒號之前。
# 正確的例子 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
3.2 其他建議
-
避免任何行末的空格。因為它通常是不可見的,它可能會令人困惑:例如反斜槓後跟空格和換行符不會作為續行標記。一些編輯器會自動去除行末空格,許多專案(如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 complex(real, imag=0.0): return magic(r=real, i=imag) # 錯誤的例子 def complex(real, imag = 0.0): return magic(r = real, i = imag)
-
函式註解中的
:
也遵循一般的:
加空格的規則,在->
兩側各使用一個空格。(參見[函式註解](####7.1 函式註解))# 正確的例子 def munge(input: AnyStr): ... def munge() -> AnyStr: ... # 錯誤的例子 def munge(input:AnyStr): ... def munge()->PosInt: ...
-
在組合使用函式註解和引數預設值時,需要在
=
兩側各使用一個空格(只有當這個引數既有函式註解,又有預設值的時候)。# 正確的例子 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()
四.何時使用行尾逗號
-
末尾逗號通常是可選的,除非在定義單元素元組
tuple
時是必需的(而且在Python 2
中,它們具有print
語句的語義)。為了清楚起見,建議使用括號(技術上來說是冗餘的)括起來。# 正確的例子 FILES = ('setup.cfg',) # 也正確,但令人困惑 FILES = 'setup.cfg',
-
當使用版本控制系統時,在將來有可能擴充套件的列表末尾新增冗餘的逗號是有好處的。具體的做法是將每一個元素寫在單獨的一行,並在行尾新增逗號,右括號單獨佔一行。但是,與有括號在同一行的末尾元素後面加逗號是沒有意義的(上述的單元素元組除外)。
# 正確的例子 FILES = [ 'setup.cfg', 'tox.ini', ] initialize( FILES, error=True, ) # 錯誤的例子 FILES = ['setup.cfg', 'tox.ini',] initialize(FILES, error=True,)
五.註釋
和程式碼矛盾的註釋還不如沒有。當代碼有改動時,一定要優先更改註釋使其保持最新。
註釋應該是完整的多個句子。如果註釋是一個短語或一個句子,其首字母應該大寫,除非開頭是一個以小寫字母開頭的識別符號(永遠不要更改識別符號的大小寫)。
如果註釋很短,結束的句號可以被忽略。塊註釋通常由一段或幾段完整的句子組成,每個句子都應該以句號結束。
在多句註釋中,除了最後一句之外,你應該在句尾的句號後再加上2個空格。
5.1 塊註釋
塊註釋一般寫在對應程式碼之前,並且和對應程式碼有同樣的縮排級別。塊註釋的每一行都應該以#
和一個空格開頭(除非該文字是在註釋內縮排對齊的)。
塊註釋中的段落應該用只含有單個#
的一行隔開。
5.2 行內註釋
儘量少用行內註釋。
行內註釋是和程式碼語句寫在一行內的註釋。行內註釋應該至少和程式碼語句之間有兩個空格的間隔,並且以#
和一個空格開始。
行內註釋通常不是必要的,在程式碼含義很明顯時甚至會讓人分心。
請不要這樣寫:
x = x + 1 # x自加
但是正確的註釋會有很大幫助:
x = x + 1 # 邊界補償(注:多用於起點為1的列表,字典等的索引之類的情況)
5.3 文件字串
要知道如何寫出好的文件字串,請參考PEP 257。
-
所有的公共模組、函式、類和方法都應該有文件字串。對於非公共方法,文件字串不是必要的,但你應該留有註釋說明該方法的功能,該註釋應當出現在
def
的下一行。 -
PEP 257描述了好的文件字串應該遵循的規則。其中最重要的是,多行文件字串以單行
"""
結尾,不能有其他字元,例如:"""Return a foobang Optional plotz says to frobnicate the bizbaz first. """
-
對於僅有一行的文件字串,結尾處的
"""
應該也寫在這一行。
六.命名規範
Python
標準庫的命名規範有一些混亂,因此我們永遠都無法保持一致。但如今仍然存在一些推薦的命名標準。新的模組和包(包括第三方框架)都應該採用這些標準,但已經存在的庫如果之前採用其他風格,更推薦保持程式碼內部的一致性。
6.1 首要原則
對於使用者可見的公共部分API,其命名應當表達出功能用途而不是其具體的實現細節。
6.2 描述:命名風格
有很多種不同的命名風格。如果能夠認出使用的是什麼命名風格將會很有幫助,這和名字被用來做什麼是獨立的。
通常區分以下命名樣式:
-
b
(單個小寫字母) -
B
(單個大寫字母) -
lowercase
(小寫字母) -
lower_case_with_underscores
(帶下劃線的小寫字母) -
UPPERCASE
(大寫字母) -
UPPER_CASE_WITH_UNDERSCORES
(帶下劃線的大寫字母) -
CapitalizedWords
(也叫做CapWords
或者CamelCase
– 因為單詞首字母大寫看起來很像駝峰)注意:當
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 *
不會import
下劃線開頭的物件。 -
single_trailing_underscore_
:以單個下劃線結尾用來避免和Python關鍵詞產生衝突,例如:Tkinter.Toplevel(master, class_="ClassName")
-
__double_leading_underscore
:以雙下劃線開頭的風格命名類屬性表示觸發命名修飾(在FooBar
類中,__boo
命名會被修飾成_FooBar__boo
)。 -
__double_leading_and_trailing_underscore__
:以雙下劃線開頭和結尾的命名風格表示“魔術”物件或屬性,存在於使用者控制的名稱空間(user-controlled namespaces)裡(也就是說,這些命名已經存在,但通常需要使用者覆寫以實現使用者所需要的功能)。 比如,__init__
,__import__
或__file__
。請依照文件描述來使用這些命名,千萬不要自己發明。
6.3 規範:命名規範
6.3.1 需要避免的命名
不要使用符號l
(L
的小寫字母),O
(o
的大寫字母),I
(i
的大寫字母)作為單字元的變數名。
在一些字型中,這些字型很難與0 或1進行區分。比如,當嘗試使用字元l
時,改成使用L
提醒替代。
6.3.2 ASCII相容性
標準庫中使用的識別符號必須與ASCII相容(參見PEP 3131中的Policy這一節)。
6.3.3 包和模組命名
模組命名應短小,且為全小寫,若下劃線能提高可讀性,也可以在模組名中使用。Python包命名也應該短小,且為全小寫,但不應使用下劃線。
當使用C/C++
寫的擴充套件模組有相應的Python
模組提供更高階的介面時(比如,更加面向物件),C/C++
模組名應該以下劃線開頭(例如,_sociket
)。
6.3.4 類命名
類命名應該使用駝峰(CapWords)的命名約定。
當介面已有文件說明且主要是被用作呼叫時,也可以使用函式的命名約定。
注意對於內建命名(builtin names)有一個特殊的約定:大部分內建名都是一個單詞(或者兩個一起使用的單詞),駝峰(CapWords)的約定只對異常命名和內建常量使用。
6.3.5 型別變數命名
PEP 484中引入的型別變數名稱通常應使用簡短的駝峰命名: T
,AnyStr
,Num
。建議將字尾_co
或_contra
新增到用於宣告相應的協變(covariant)和逆變(contravariant)的行為。例如:
from typing import TypeVar
VT_co = TypeVar('VT_co', covariant=True')
KT_contra = TypeVar('KT_contra', contravariant=True)
6.3.6 異常命名
因為異常是類,類的命名規範可以應用在這裡。但是,你應該在異常名上使用Error
字尾(如果這個異常確實是一個Error
)。
6.3.7 全域性變數命名
(在此之前,我們先假定這些變數都僅在同一個模組內使用。)這些規範同樣也適用於函式命名。
對於引用方式設計為from M import *
的模組,應該使用__all__
機制來避免import
全域性變數,或者採用下劃線字首的舊規範來命名全域性變數,從而表明這些變數是“模組非公開的”。
6.3.8 函式和變數命名
函式命名應該都是小寫,必要時使用下劃線來提高可讀性。
變數命名和函式命名相同的規則。
只有當已有程式碼風格已經是混合大小寫時(比如threading.py
),為了保留向後相容性才使用混合大小寫。
6.3.9 函式和方法引數
例項方法的第一引數永遠都是self
。
類方法的第一個引數永遠都是cls
。
在函式引數名和保留關鍵字衝突時,相對於使用縮寫或拼寫簡化,使用以下劃線結尾的命名一般更好。比如,class_
比class
更好。(或許使用同義詞避免這樣的衝突是更好的方式。)
6.3.10 方法命名和例項變數
使用函式命名的規則:小寫單詞,必要時使用下劃線分開以提高可讀性。
僅對於非公開方法和變數命名在開頭使用一個下劃線。
避免和子類的命名衝突,使用兩個下劃線開頭來觸發Python
的命名修飾機制。
Python
類名的命名修飾規則:如果類Foo
有一個屬性叫__a
,不能使用Foo.__a
的方式訪問該變數(在類的內部可以通過self.__a
的方式去訪問,在外部有使用者可能仍然堅持使用Foo._Foo__a
的方法訪問)。一般來說,兩個下劃線開頭的命名方法僅用於避免與設計為子類的類中的屬性名衝突。
注意:關於
__names
的使用也有一些爭論。
6.3.11 常量
常量通常是在模組級別定義的,使用全部大寫並用下劃線將單詞分開。如:MAX_OVERFLOW
和TOTAL
。
6.3.12 繼承的設計
記得永遠區別類的方法和例項變數(屬性)應該是公開的還是非公開的。如果有疑慮的話,請選擇非公開的;因為之後將非公開屬性變為公開屬性要容易些。
公開屬性是那些你希望和你定義的類無關的客戶來使用的,並且確保不會出現向後不相容的問題。非公開屬性是那些不希望被第三方使用的部分,你可以不用保證非公開屬性不會變化或被刪除。
我們在這裡沒有使用“私有private
”這個詞,因為在Python
裡沒有什麼屬性是真正私有的(這樣設計省略了大量不必要的工作)。
另一類屬性屬於子類API的一部分(在其他語言中經常被稱為protected
)。一些類是為繼承設計的,要麼擴充套件要麼修改類的部分行為。當設計這樣的類時,需要謹慎明確地決定哪些屬性是公開的,哪些屬於子類API,哪些只會被你的基類呼叫。
請記住以上幾點,下面是Python
規範的指南:
-
公開屬性不應該有開頭下劃線。
-
如果公開屬性的名字和保留關鍵字有衝突,在你的屬性名尾部加上一個下劃線。這比採用縮寫和簡寫更好。(然而,和這條規則衝突的是,
cls
對任何變數和引數來說都是一個更好地拼寫,因為大家都知道這表示class
,特別是在類方法的第一個引數裡。)注意:對於類方法,參考之前的函式和方法引數命名建議。
-
對於簡單的公共資料屬性,最後僅公開屬性名字,不要公開復雜的呼叫或設值方法。請記住,如果你發現一個簡單的資料屬性需要增加功能行為時,
Python
為功能增強提供了一個簡單的途徑。這種情況下,使用Property
註解將功能實現隱藏在簡單資料屬性訪問語法之後。注意 1:
Property
註解僅僅對新規範類有用。注意 2:儘量保證功能行為沒有副作用,儘管快取這種副作用看上去並沒有什麼大問題。
注意 3:對計算量大的運算避免使用
property
;屬性的註解會讓呼叫者相信訪問的運算量是相對較小的。 -
如果你的類將被子類繼承的話,你有一些屬性並不想讓子類訪問,考慮將他們命名為兩個下劃線開頭並且結尾處沒有下劃線。這樣會觸發
Python
命名修飾演算法,類名會被修飾新增到屬性名中。這樣可以避免屬性命名衝突,以免子類會不經意間包含相同的命名。注意 1:注意命名修飾僅僅是簡單地將類名加入到修飾名中,所以如果子類有相同的類名和屬性名,你可能仍然會遇到命名衝突問題。
注意 2:命名修飾可能導致某些用途不太方便,比如除錯和
__getattr__()
。然而命名修飾演算法可以更好的被記錄,並且易於手動執行。注意 3:不是所有人都喜歡命名修飾。需要試著去平衡避免偶然命名衝突的需求和高階呼叫者使用的潛在可能性。
6.4 公開和內部介面
任何向後相容性保證僅對公開介面適用。因此,使用者能夠清楚分辨公開介面和內部介面是很重要的。
文件化的介面被認為是公開的,除非文件中明確聲明瞭它們是臨時介面或者內部介面不受通常的向後相容性保證。所有文件中未提到的介面都應該被認為是內部的。
為了更好審視公開介面和內部介面,模組應該在__all
屬性中明確宣告公開API是哪些。將__all__
設為空列表表示該模組中沒有公開API。
即使正確設定了__all__
屬性,內部介面(包,模組,類,函式,屬性或其他命名)也應該以單下劃線開頭。
如果介面的任一一個名稱空間(包,模組或類)是內部的,那麼該介面也應該是內部的。
匯入的命名應該永遠被認為是實現細節。其他模組不應當依賴這些非直接訪問的匯入命名,除非它們在文件中明確地被寫為模組的API,例如那些從子模組公開的功能os.path
或者包的__init__
模組。
七.程式設計建議
-
程式碼應該以不影響其他
Python
(PyPy
,Jython
,IronPython
,Cython
,Psyco
等)實現的方式編寫。例如,不要依賴於
CPython
在字串拼接時的優化實現,形如這種語句形式a += b
和a = a + b
。即使是CPython
(僅對某些型別起作用) 這種優化也是脆弱的,而且在不使用引用計數的實現中根本不存在優化。在庫中效能敏感的部分,用''.join
形式來代替。這會確保在所有不同的實現中字串拼接是線性時間的。 -
與單例作比較,像
None
應該用is
或is not
,從不使用==
操作符。另外,當你真的要判斷
x
是不是None
時當心if x is not None
這樣的寫法。例如,測試一個預設值為None
的變數或引數是否設定成了其它值,其它值有可能是某種特殊型別(如容器),這種特殊型別在邏輯運算時其值會被當作Flase
來看待。 -
用
is not
操作符而不是not ... is
。雖然這兩個表示式是功能相同的,但是前者更易讀,是首選。# 正確的例子 if foo is not None # 錯誤的例子 if not foo is None
-
用大量的比較實現排序操作的時候,最好實現所有的比較操作符(
__eq__
、__ne__
、__lt__
,__le__
,__gt__
,__ge__
),而不是依靠其他程式碼來進行特定比較。為了最大限度的減少工作量,
functools.total_ordering()
裝飾器提供了一個工具去生成缺少的比較方法。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
語句可以提供的唯一好處(即lambda
能鑲嵌在一個很長的表示式裡)。 -
異常類應派生自
Exception
而不是BaseException
。直接繼承BaseException
是為Exception
保留的,從BaseException
繼承並捕獲異常這種做法幾乎總是錯的。設計異常的層次結構,應基於那些可能出現異常的程式碼,而不是引發異常的位置。編碼的時候,以回答“出了什麼問題?”為目標,而不是僅僅指出“這裡出現了問題”(見 PEP 3151 一個內建異常結構層次的例子)。
類的命名規範適用於異常,如果異常類是一個錯誤,你應該給異常類加一個字尾
Error
。用於非本地流程控制或者其他形式訊號的非錯誤異常不需要一個特殊的字尾。 -
適當的使用異常鏈。在
Python 3
裡,應該使用raise X from Y
來指示顯式替換,而不會丟失原始的追溯。當有意替換一個內部的異常時(在
Python 2
用raise X
,Python 3.3+ 用raise X from None
),請確保將相關詳細資訊轉移到新異常中(例如,將KeyError
轉換為AttributeError
時保留屬性名稱,或將原始異常的文字嵌入到新的異常訊息中)。 -
捕獲異常時,儘可能使用明確的異常,而不是用一個空的
except:
語句。例如:try: import platform_specific_module except ImportError: platform_specific_module = None
一個空的
except:
語句將會捕獲到SystemExit
和KeyboardInterrupt
異常,很難區分程式的中斷到底是Ctrl+C
還是其他問題引起的。如果你想捕獲程式的所有錯誤,使用except Exception:
(空except:
等同於except BaseException
)。一個好的經驗是限制使用空
except
語句,除了這兩種情況:- 如果異常處理程式會打印出或者記錄回溯資訊;至少使用者意識到錯誤的存在。
- 如果程式碼需要做一些清理工作,但後面用
raise
向上丟擲異常。try ... finally
是處理這種情況更好的方式。
-
繫結異常給一個名字時,最好使用
Python 2.6
裡新增的明確的名字繫結語法:try: process_data() except Exception as exc: raise DataProcessingFailedError(str(exc))
Python 3
只支援這種語法,避免與基於逗號的舊式語法產生二義性。 -
捕獲作業系統異常時,最好使用
Python 3.3
裡引進的明確的異常結構層次,而不是內省的errno
值。 -
另外,對於所有
try
/except
子句,將try
子句限制為必需的絕對最小程式碼量。同樣,這樣可以避免遮蔽錯誤。# 正確的例子 try: value = collection[key] except KeyError: return key_not_found(key) else: return handle_value(value) # 錯誤的例子 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
也是可以接受的。 -
當它們做一些除了獲取和釋放資源之外的事的時候,上下文管理器應該通過單獨的函式或方法呼叫。例如:
# 正確的例子 with conn.begin_transaction(): do_stuff_in_transaction(conn) # 錯誤的例子 with conn: do_stuff_in_transaction(conn)
第二個例子沒有提供任何資訊來表明
__enter__
和__exit__
方法在完成一個事務後做了一些除了關閉連線以外的其它事。在這種情況下明確是很重要的。 -
堅持使用
return
語句。函式內的return
語句都應該返回一個表示式,或者None
。如果一個return
語句返回一個表示式,另一個沒有返回值的應該用return None
清晰的說明,並且在一個函式的結尾應該明確使用一個return
語句(如果有返回值的話)。# 正確的例子 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)
-
用字串方法代替字串模組。
字串方法總是快得多,並且與
unicode
字串共享相同的API。如果需要與2.0以下的Python
向後相容,則覆蓋此規則。 -
用
''.startswith()
和''.endswith()
代替字串切片來檢查字首和字尾。startswith()
和endswith()
是更簡潔的,不容易出錯的。例如:# 正確的例子 if foo.startswith('bar'): # 錯誤的例子 if foo[:3] == 'bar':
-
物件型別的比較應該始終使用
isinstance()
而不是直接比較。# 正確的例子 if isinstance(obj, int): # 錯誤的例子 if type(obj) is type(1):
當比較一個物件是不是字串時,記住它有可能也是一個
unicode
字串!在Python 2
裡面,str
和unicode
有一個公共的基類叫basestring
,因此你可以這樣做:if isinstance(obj, basestring):
注意:在
Python 3
裡面,unicode
和basestring
已經不存在了(只有str
),byte
物件不再是字串的一種(被一個整數序列替代)。 -
對於序列(字串、列表、元組)來說,空的序列為
False
。# 正確的例子 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
7.1 函式註解
隨著PEP 484被正式接受,函式註釋的樣式規則已經改變。
-
為了向前相容,
Python 3
程式碼中的函式註釋最好使用PEP 484語法。 -
建議不再使用在此文件早期版本中描述的試驗性質的註解樣式。
-
然而,在標準庫
stdlib
之外,現在鼓勵在PEP 484的規則範圍內的實驗。例如,使用PEP 484樣式型別的註解標記大型第三方庫或應用程式,評估新增這些註解的方式是否簡便,並觀察其存在是否增加了程式碼可讀性。 -
Python
標準庫在採用這些註解時應持謹慎態度,但是當編寫新程式碼或進行大的重構時,允許使用。 -
如果希望不按照函式註解的方式來使用函式,可以在檔案頭部新增以下注釋:
# type: ignore
這會告訴型別檢查器忽略所有註解。(在PEP 484中可以找到更細緻的方式來控制型別檢查器的行為。)
-
像程式碼掃描工具一樣
linters
,型別檢查器是可選的、單獨的工具。預設情況下,Python
直譯器不應該由於型別檢查而發出任何訊息,並且不應該根據註釋來改變它們的行為。 -
不想使用型別檢查器的使用者可以忽略它們。但是,預計第三方庫軟體包的使用者可能希望在這些軟體包上執行型別檢查器。為此,PEP 484 建議使用存根檔案:
.pyi
檔案,型別檢查器優先於相應的.py
檔案讀取這個檔案。存根檔案可以與庫一起分發,也可以單獨地(在庫作者許可的情況下)通過Typeshed repo
分發。 -
對於需要向後相容的程式碼,可以以註釋的形式新增型別註解。參見PEP 484的相關章節。
7.2 變數註解
PEP 526 引入了變數註解。對於變數註解的推薦樣式類似於上面的函式註解:
- 對於模組層的變數,類或例項變數,和區域性變數的註解應該在冒號之後有一個空格。
- 在冒號之前應該沒有空格。
- 如果賦值有右側的值,那麼等號兩側都應該有一個空格。
# 正確的例子
code: int
class Point:
coords: Tuple[int, int]
label: str = '<unknown'
# 錯誤的例子
code:int # No space after colon
code : int # Space before colon
class Test:
result: int=0 # No spaces around equality sign