Python PEP8代碼規範_20180614
PEP8 代碼風格指南
知識點
- 代碼排版
- 字符串引號
- 表達式和語句中的空格
- 註釋
- 版本註記
- 命名約定
- 公共和內部接口
- 程序編寫建議
1. 介紹
這份文檔給出的代碼約定適用於主要的 Python 發行版所有標準庫中的 Python 代碼。請參閱相似的 PEP 信息,其用於描述實現 Python 的 C 代碼規範[1]。
這份文檔和 PEP 257(文檔字符串約定) 改編自 Guido 的 Python 風格指南原文,從 Barry 的風格指南裏添加了一些東西[2]。
隨著時間的推移,這份額外約定的風格指南已經被認可了,過去的約定由於語言自身的發展被淘汰了。
許多項目有它們自己的編碼風格指南。如果有沖突,優先考慮項目規定的編碼指南。
2. 愚蠢的一致性就像沒腦子的妖怪
Guido 的一個主要見解是讀代碼多過寫代碼。這裏提供指南的意圖是強調代碼可讀性的重要性,並且使大多數 Python 代碼保持一致性。如 PEP 20 所述,“Readability counts”。
風格指南是關於一致性的。風格一致對於本指南來說是重要的,對一個項目來說是更重要的,對於一個模塊或者方法來說是最重要的。
但是最最重要的是:知道什麽時候應該破例–有時候這份風格指南就是不適用。有疑問時,用你最好的判斷力,對比其它的例子來確定這是不是最好的情況,並且不恥下問。
特別說明:不要為了遵守這份風格指南而破壞代碼的向後兼容性。
這裏有一些好的理由去忽略某個風格指南:
- 當應用風格指南的時候使代碼更難讀了,對於嚴格依循風格指南的約定去讀代碼的人也是不應該的。
- 為了保持和風格指南的一致性同時也打破了現有代碼的一致性(可能是歷史原因)–雖然這也是一個整理混亂代碼的機會(現實中的 XP 風格)。
- 因為問題代碼的歷史比較久遠,修改代碼就沒有必要性了。
- 當代碼需要與舊版本的 Python 保持兼容,而舊版 Python 又不支持風格指南中提到的特性的時候。
3. 代碼排版
3.1. 縮進
每層縮進使用4個空格。
續行要麽與圓括號、中括號、花括號這樣的被包裹元素保持垂直對齊,要麽放在 Python 的隱線(註:應該是相對於def的內部塊)內部,或者使用懸掛縮進。使用懸掛縮進的註意事項:第一行不能有參數,用進一步的縮進來把其他行區分開。
好的:
# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
var_three, var_four)
# More indentation included to distinguish this 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)
不好的:
# 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 語句的條件部分足夠長,需要將它寫入到多個行,值得註意的是兩個連在一起的關鍵字(i.e. if),添加一個空格,給後續的多行條件添加一個左括號形成自然地4空格縮進。如果和嵌套在 if 語句內的縮進代碼塊產生了視覺沖突,也應該被自然縮進4個空格。這份增強建議書對於怎樣(或是否)把條件行和 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‘,
)
3.2. 制表符還是空格?
空格是首選的縮進方法。
制表符(Tab)應該被用在那些以前就使用了制表符縮進的地方。
Python 3 不允許混合使用制表符和空格來縮進代碼。
混合使用制表符和空格縮進的 Python 2 代碼應該改為只使用空格。
當使用-t
選項來調用 Python 2 的命令行解釋器的時候,會對混合使用制表符和空格的代碼發出警告。當使用-tt
選項的時候,這些警告會變成錯誤。這些選項是強烈推薦的!
3.3. 每行最大長度
限制每行的最大長度為79個字符。
對於那些約束很少的文本結構(文檔字符串或註釋)的長塊,應該限制每行長度為72個字符。
限制編輯窗口的寬度使並排打開兩個窗口成為可能,使用通過代碼審查工具時,也能很好的通過相鄰列展現不同代碼版本。
一些工具的默認換行設置打亂了代碼的可視結構,使其更難理解。限制編輯器窗口寬為80來避免自動換行,即使有些編輯工具在換行的時候會在最後一列放一個標識符。一些基於 Web 的工具可能根本就不提供動態換行。
一些團隊更傾向於長的代碼行。對於達成了一致意見來統一代碼的團隊而言,把行提升到80~100的長度是可接受的(實際最大長度為99個字符),註釋和文檔字符串的長度還是建議在72個字符內。
Python 標準庫是非常專業的,限制最大代碼長度為79個字符(註釋和文檔字符串最大長度為72個字符)。
首選的換行方式是在括號(小中大)內隱式換行(非續行符\
)。長行應該在括號表達式的包裹下換行。這比反斜杠作為續行符更好。
反斜杠有時仍然適用。例如,多個很長的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
語句。
確保續行的縮進是恰到好處的。遇到二元操作符,首選的斷行位置是操作符的後面而不是前面。這有一些例子:
class Rectangle(Blob):
def __init__(self, width, height,
color=‘black‘, emphasis=None, highlight=0):
if (width == 0 and height == 0 and
color == ‘red‘ and emphasis == ‘strong‘ or
highlight > 100):
raise ValueError("sorry, you lose")
if width == 0 and height == 0 and (color == ‘red‘ or
emphasis is None):
raise ValueError("I don‘t think so -- values are %s, %s" %
(width, height))
Blob.__init__(self, width, height,
color, emphasis, highlight)
3.4. 空行
頂級函數和類定義上下使用兩個空行分隔。
類內的方法定義使用一個空行分隔。
可以使用額外的空行(有節制的)來分隔相關聯的函數組。在一系列相關聯的單行代碼中空行可以省略(e.g. 一組虛擬的實現)。
在函數中使用空白行(有節制的)來表明邏輯部分。
Python 接受使用換頁符(i.e. Ctrl+L
)作為空格;許多工具都把Ctrl+L
作為分頁符,因此你可以用它們把你的文件中相似的章節分頁。註意,一些編輯器和基於 Web 的代碼查看工具可能不把Ctrl+L
看做分頁符,而是在這個位置放一個其它的符號。
3.5. 源文件編碼
在核心 Python 發布版中的代碼應該總是使用UTF-8
編碼(或者在 Python 2 中使用ASCII
)。
使用ASCII
(Python 2)或UTF-8
(Python 3)的文件不需要有編碼聲明(註:它們是默認的)。
在標準庫中,非缺省的編碼應該僅僅用於測試目的,或者註釋或文檔字符串中的作者名包含非ASCII
碼字符;否則,優先使用\x
、\u
、\U
或者\N
來轉義字符串中的非ASCII
數據。
對於 Python 3.0 和之後的版本,以下是有關標準庫的政策(見PEP 3131):所有 Python 標準庫中的標識符必須使用只含ASCII
的標識,並且應該使用英語單詞只要可行(在多數情況下,縮略語和技術術語哪個不是英語)。此外,字符串和註釋也必須是ASCII
。僅有的例外是:(a)測試用例測試非ASCII
特性時,(b)作者名。作者的名字不是基於拉丁字母的必須提供他們名字的拉丁字母音譯。
面向全球用戶的開源項目,鼓勵采取相似的政策。
3.6. 導入包
-
import
不同的模塊應該獨立一行,如:好的:
import os
import sys
??不好的:
import sys, os
??這樣也是可行的:
from subprocess import Popen, PIPE
-
import
語句應該總是放在文件的頂部,在模塊註釋和文檔字符串之下,在模塊全局變量和常量之前。import
語句分組順序如下:- 導入標準庫模塊
- 導入相關第三方庫模塊
- 導入當前應用程序/庫模塊
每組之間應該用空行分開。
然後用
__all__
聲明本文件內的模塊。 -
絕對導入是推薦的,它們通常是更可讀的,並且在錯誤的包系統配置(如一個目錄包含一個以
os.path
結尾的包)下有良好的行為傾向(至少有更清晰的錯誤消息):
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
??當然,相對於絕對導入,相對導入是個可選替代,特別是處理復雜的包結構時,絕對導入會有不必要的冗余:
from . import sibling
from .sibling import example
??標準庫代碼應該避免復雜的包結構,並且永遠使用絕對導入。
??應該從不使用隱式的相對導入,而且在 Python 3 中已經被移除。
- 從一個包含類的模塊導入類時,這樣寫通常是可行的:
from myclass import MyClass
from foo.bar.yourclass import YourClass
??如果上面的方式會本地導致命名沖突,則這樣寫:
import myclass
import foo.bar.yourclass
??以myclass.MyClass
和foo.bar.yourclass.YourClass
這樣的方式使用。
-
應該避免通配符導入(
from import *
),這會使名稱空間裏存在的名稱變得不清晰,迷惑讀者和自動化工具。這裏有一個可辯護的通配符導入用例,,重新發布一個內部接口作為公共 API 的一部分(例如,使用純 Python 實現一個可選的加速器模塊的接口,但並不能預知這些定義會被覆蓋)。當以這種方式重新發布名稱時,下面關於公共和內部接口的指南仍然適用。
4. 字符串引號
在 Python 裏面,單引號字符串和雙引號字符串是相同的。這份指南對這個不會有所建議。選擇一種方式並堅持使用。一個字符串同時包含單引號和雙引號字符時,用另外一種來包裹字符串,而不是使用反斜杠來轉義,以提高可讀性。
對於三引號字符串,總是使用雙引號字符來保持與文檔字符串約定的一致性(PEP 257)。
5. 表達式和語句中的空格
5.1. 不能忍受的情況
避免在下列情況中使用多余的空格:
- 與括號保持緊湊(小括號、中括號、大括號):
Yes: spam(ham[1], {eggs: 2})
No: spam( ham[ 1 ], { eggs: 2 } )
- 與後面的逗號、分號或冒號保持緊湊:
Yes: if x == 4: print x, y; x, y = y, x
No: 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]
- 函數名與其後參數列表的左括號應該保持緊湊:
Yes: spam(1)
No: spam (1)
- 與切片或索引的左括號保持緊湊:
Yes: dct[‘key‘] = lst[index]
No: dct [‘key‘] = lst [index]
-
在復制操作符(或其它)的兩側保持多余一個的空格:
好的:
x = 1
y = 2
long_variable = 3
??不好的:
x = 1
y = 2
long_variable = 3
5.2. 其他建議
-
總是在這些二元操作符的兩側加入一個空格:賦值(=),增量賦值(+=, -= etc.),比較(==, <, >, !=, <>, <=, >=, 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)
-
【註:Python 3】帶註釋的函數定義中的等號兩側要各插入空格。此外,在冒號後用一個單獨的空格,也要在表明函數返回值類型的
->
左右各插入一個空格。好的:
def munge(input: AnyStr):
def munge(sep: AnyStr = None):
def munge() -> AnyStr:
def munge(input: AnyStr, sep: AnyStr = None, limit=1000):
??不好的:
def munge(input: AnyStr=None):
def munge(input:AnyStr):
def munge(input: AnyStr)->PosInt:
-
打消使用復合語句(多條語句在同一行)的念頭。
好的:
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()
6. 註釋
與代碼相矛盾的註釋不如沒有。註釋總是隨著代碼的變更而更新。
註釋應該是完整的句子。如果註釋是一個短語或語句,第一個單詞應該大寫,除非是一個開頭是小寫的標識符(從不改變標識符的大小寫)。
如果註釋很短,末尾的句點可以省略。塊註釋通常由一個或多個有完整句子的段落組成,並且每個句子應該由句點結束。
你應該在一個句子的句點後面用兩個空格。
寫英語時,遵循《Strunk and White》(註:《英文寫作指南》,參考維基百科)。
來自非英語國家的程序員:請用英語寫註釋,除非你120%確定你的代碼永遠不會被那些不說你的語言的人閱讀。
6.1. 塊註釋
塊註釋通常用來說明跟隨在其後的代碼,應該與那些代碼有相同的縮進層次。塊註釋每一行以#
起頭,並且#
後要跟一個空格(除非是註釋內的縮進文本)。
6.2. 行內註釋
有節制的使用行內註釋。
一個行內註釋與語句在同一行。行內註釋應該至少與語句相隔兩個空格。以#
打頭,#
後接一個空格。
無謂的行內註釋如果狀態明顯,會轉移註意力。不要這樣做:
x = x + 1 # Increment x
但有的時候,這樣是有用的:
x = x + 1 # Compensate for border
6.3. 文檔字符串
編寫良好的文檔字符串(a.k.a “docstring”)的約定常駐在 PEP 257
- 為所有的公共模塊、函數、類和方法編寫文檔字符串。對於非公共的方法,文檔字符串是不必要的,但是也應該有註釋來說明代碼是幹什麽的。這個註釋應該放在方法聲明的下面。
- PEP 257 描述了良好的文檔字符串的約定。註意,文檔字符串的結尾
"""
應該放在單獨的一行,例如:
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
- 對於單行的文檔字符串,把結尾
"""
放在同一行。
7. 版本註記
如果必須要 Subversion,CVS 或 RCS 標記在你的源文件裏,像這樣做:
__version__ = "$Revision$"
# $Source$
這幾行應該在模塊的文檔字符串後面,其它代碼的前面,上下由一個空行分隔。
8. 命名約定
Python 庫的命名規則有點混亂,因此我們永遠也不會使其完全一致的 – 不過,這裏有一些當前推薦的命名標準。新的模塊和包(包括第三方框架)應該按照這些標準來命名,但是已存在庫有不同的風格,內部一致性是首選。
8.1. 覆蓋原則
API 裏對用戶可見的公共部分應該遵循約定,反映的是使用而不是實現。
8.2. 規定:命名約定
有許多不同的命名風格。這有助於識別正在使用的命名風格,獨立於它們的用途。
下面的命名風格通常是有區別的:
- b (一個小寫字母)
- B (一個大寫字母)
- lowercase
- lower_case_with_underscores
- UPPERCASE
- UPPER_CASE_WITH_UNDERSCORES
- CapitalizedWords (又叫 CapWords,或者 CamelCase(駱駝命名法) – 如此命名因為字母看起來崎嶇不平[3]。有時候也叫 StudlyCaps。
??註意:在 CapWords 使用縮略語時,所有縮略語的首字母都要大寫。因此HTTPServerError
比HttpServerError
要好。
- 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_trailingunderscore : 通常是為了避免與 Python 規定的關鍵字沖突,如
Tkinter.Toplevel(master, class_=‘ClassName‘)
。 - double_leading_underscore : 命名一個類屬性,調用的時候名字會改變(在類
FooBar
中,`boo變成了
_FooBar__boo`;見下)。 - double_leading_and_trailing_underscore :”魔術”對象或屬性,活在用戶控制的命名空間裏。例如,
__init__
,__import__
和__file__
。永遠不要像這種方式命名;只把它們作為記錄。
8.3. 規定:命名約定
8.3.1. 應該避免的名字
永遠不要使用單個字符l
(小寫字母 el),O
(大寫字母 oh),或I
(大寫字母 eye)作為變量名。
在一些字體中,這些字符是無法和數字1
和0
區分開的。試圖使用l
時用L
代替。
8.3.2. 包和模塊名
模塊名應該短,且全小寫。如果能改善可讀性,可以使用下劃線。Python 的包名也應該短,全部小寫,但是不推薦使用下劃線。
因為模塊名就是文件名,而一些文件系統是大小寫不敏感的,並且自動截斷長文件名,所以給模塊名取一個短小的名字是非常重要的 – 在 Unix 上這不是問題,但是把代碼放到老版本的 Mac, Windows,或者 DOS 上就可能變成一個問題了。
用 C/C++ 給 Python 寫一個高性能的擴展(e.g. more object oriented)接口的時候,C/C++ 模塊名應該有一個前導下劃線。
8.3.3. 類名
類名通常使用 CapWords 約定。
The naming convention for functions may be used instead in cases where the interface is documented and used primarily as a callable.
註意和內建名稱的區分開:大多數內建名稱是一個單獨的單詞(或兩個單詞一起),CapWords 約定只被用在異常名和內建常量上。
8.3.4. 異常名
因為異常應該是類,所以類名約定在這裏適用。但是,你應該用Error
作為你的異常名的後綴(異常實際上是一個錯誤)。
8.3.5. 全局變量名
(我們希望這些變量僅僅在一個模塊內部使用)這個約定有關諸如此類的變量。
若被設計的模塊可以通過from M import *
來使用,它應該使用__all__
機制來表明那些可以可導出的全局變量,或者使用下劃線前綴的全局變量表明其是模塊私有的。
8.3.6. 函數名
函數名應該是小寫的,有必要的話用下劃線來分隔單詞提高可讀性。
混合大小寫僅僅在上下文都是這種風格的情況下允許存在(如thread.py),這是為了維持向後兼容性。
8.3.7. 函數和方法參數
總是使用self
作為實例方法的第一個參數。
總是使用cls
作為類方法的第一個參數。
如果函數參數與保留關鍵字沖突,通常最好在參數後面添加一個尾隨的下劃線,而不是使用縮寫或胡亂拆減。因此class_
比clss
要好。(或許避免沖突更好的方式是使用近義詞)
8.3.8. 方法名和實例變量
用函數名的命名規則:全部小寫,用下劃線分隔單詞提高可讀性。
用一個且有一個前導的下劃線來表明非公有的方法和實例變量。
為了避免與子類變量或方法的命名沖突,用兩個前導下劃線來調用 Python 的命名改編規則。
Python 命名改編通過添加一個類名:如果類Foo
有一個屬性叫__a
,它不能被這樣Foo.__a
訪問(執著的人可以通過這樣Foo._Foo__a
來訪問)通常,雙前導的下劃線應該僅僅用來避免與其子類屬性的命名沖突。
註意:這裏有一些爭議有關__names
的使用(見下文)。
8.3.9. 常量
常量通常是模塊級的定義,全部大寫,單詞之間以下劃線分隔。例如MAX_OVERFLOW
和TOTAL
。
8.3.10. 繼承的設計
總是決定一個類的方法和變量(屬性)是應該公有還是非公有。如果有疑問,選擇非公有;相比把共有屬性變非公有,非公有屬性變公有會容易得多。
公有屬性是你期望給那些與你的類無關的客戶端使用的,你應該保證不會出現不向後兼容的改變。非公有的屬性是你不打算給其它第三方使用的;你不需要保證非公有的屬性不會改變甚至被移除也是可以的。
我們這裏不適用“私有”這個術語,因為在 Python 裏沒有真正的私有屬性(一般沒有不必要的工作量)。
另一種屬性的分類是“子類 API”的一部分(通常在其它語言裏叫做“Protected”)。一些類被設計成被繼承的,要麽擴展要麽修改類的某方面行為。設計這樣一個類的時候,務必做出明確的決定,哪些是公有的,其將會成為子類 API 的一部分,哪些僅僅是用於你的基類的。
處於這種考慮,給出 Pythonic 的指南:
- 共有屬性不應該有前導下劃線。
- 如果你的公有屬性與保留關鍵字發生沖突,在你的屬性名後面添加一個尾隨的下劃線。這比使用縮寫或胡亂拆減要好。(盡管這條規則,已知某個變量或參數可能是一個類情況下,
cls
是首選的命名,特別是作為類方法的第一個參數)
??註意一:見上面推薦的類方法參數命名方式。
- 對於簡單的公有數據屬性,最好的方式是暴露屬性名,不要使用復雜的訪問屬性/修改屬性的方法。記住,Python 提供了捷徑去提升特性,如果你發現簡單的數據屬性需要增加功能行為。在這種情況下,使用
properties
把功能實現隱藏在簡單的數據屬性訪問語法下面。
??註意一:properties
僅僅在新式類下工作。 ??註意二:盡量保持功能行為無邊際效應,然而如緩存有邊際效應也是好的。 ??註意三:避免為計算開銷大的操作使用properties
;屬性標記使調用者相信這樣來訪問(相對來說)是開銷很低的。
- 如果你的類是為了被繼承,你有不想讓子類使用的屬性,給屬性命名時考慮給它們加上雙前導下劃線,不要加尾隨下劃線。這會調用 Python 的名稱重整算法,把類名加在屬性名前面。避免了命名沖突,當子類不小心命名了和父類屬性相同名稱的時候。
??註意一:註意只是用了簡單的類名來重整名字,因此如果子類和父類同名的時候,你仍然有能力避免沖突。
??註意二:命名重整有確定的用途,例如調試和__getattr__()
,就不太方便。命名重整算法是有據可查的,易於手動執行。
??註意三:不是每個人都喜歡命名重整。盡量平衡名稱的命名沖突與面向高級調用者的潛在用途。
9. 公共和內部接口
保證所有公有接口的向後兼容性。用戶能清晰的區分公有和內部接口是重要的。
文檔化的接口考慮公有,除非文檔明確的說明它們是暫時的,或者內部接口不保證其的向後兼容性。所有的非文檔化的應該被假設為非公開的。
為了更好的支持內省,模塊應該用__all__
屬性來明確規定公有 API 的名字。設置__all__
為空list
表明模塊沒有公有 API。
甚至與__all__
設置相當,內部接口(包、模塊、類、函數、屬性或者其它的名字)應該有一個前導的下劃線前綴。
被認為是內部的接口,其包含的任何名稱空間(包、模塊或類)也被認為是內部的。
導入的名稱應始終視作一個實現細節。其它模塊不能依賴間接訪問這些導入的名字,除非它們是包含模塊的 API 明確記載的一部分,例如os.path
或一個包的__init__
模塊暴露了來自子模塊的功能。
10. 程序編寫建議
-
代碼的編寫方式不能對其它 Python 的實現(PyPy、Jython、IronPython、Cython、Psyco,諸如此類的)不利。
例如,不要依賴於 CPython 在字符串拼接時的優化實現,像這種語句形式
a += b
和a = a + b
。即使是 CPython(僅對某些類型起作用) 這種優化也是脆弱的,不是在所有的實現中都不使用引用計數。在庫中性能敏感的部分,用‘‘.join
形式來代替。這會確保在所有不同的實現中字符串拼接是線性時間的。 -
比較單例,像
None
應該用is
或is not
,從不使用==
操作符。當你的真正用意是
if x is not None
的時候,當心if x
這樣的寫法 – 例如,測試一個默認值為None
的變量或參數是否設置成了其它值,其它值可能是那些布爾值為 false 的類型(如空容器)。 -
用
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
表達式可以提供一個顯示的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
時保留屬性名,或者把原始異常的錯誤信息嵌在新異常裏)。 -
在 Python 2 裏拋出異常時,用
raise ValueError(‘message‘)
代替舊式的raise ValueError, ‘message‘
。在 Python 3 之後的語法裏,舊式的異常拋出方式是非法的。
使用括號形式的異常意味著,當你傳給異常的參數過長或者包含字符串格式化時,你就不需要使用續行符了,這要感謝括號!
-
捕獲異常時,盡可能使用明確的異常,而不是用一個空的
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。如果需要向後兼容性覆蓋這個規則,需要 Python 2.0 以上的版本。
-
用
‘‘.startswith()
和‘‘.endswith()
代替字符串切片來檢查前綴和後綴。startswith()
和endswith()
是更簡潔的,不容易出錯的。例如:
Yes: if foo.startswith(‘bar‘):
No: if foo[:3] == ‘bar‘:
- 對象類型的比較應該始終使用
isinstance()
而不是直接比較。
Yes: if isinstance(obj, int):
No: 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):
- 不要讓字符串對尾隨的空格有依賴。這樣的尾隨空格是視覺上無法區分的,一些編輯器(or more recently, reindent.py)會將其裁剪掉。
- 不要用
==
比較True
和False
。
Yes: if greeting:
No: if greeting == True:
Worse: if greeting is True:
-
Python 標準庫將不再使用函數標註,以至於給特殊的標註風格給一個過早的承若。代替的,這些標註是留給用戶去發現和體驗的有用的標註風格。
建議第三方實驗的標註用相關的修飾符指示標註應該如何被解釋。
早期的核心開發者嘗試用函數標註顯示不一致、特別的標註風格。例如:
[str]
是很含糊的,它可能代表一個包含字符串的列表,也可能代表一個為字符串或為空的值。open(file:(str,bytes))
可能用來表示file
的值可以是一個str
或者bytes
,也可能用來表示file
的值是一個包含str
和bytes
的二元組。- 標註
seek(whence:int)
體現了一個過於明確又不夠明確的混合體:int
太嚴格了(有__index__
的應該被允許),又不夠嚴格(只有0,1,2是被允許的)。同樣的,標註write(b: byte)
太嚴格了(任何支持緩存協議的都應該被允許)。 - 像
read1(n: int=None)
這樣的標註自我矛盾,因為None
不是int
。像source_path(self, fullname:str) -> object
標註是迷惑人的,返回值到底是應該什麽類型? - 除了上面之外,在具體類型和抽象類型的使用上是不一致的:
int
對integral
(整數),set/fronzenset
對MutableSet/Set
。 - 不正確的抽象基類標註規格。例如,集合之間的操作需要另一個對象是集合的實例,而不只是一個可叠代序列。
- 另一個問題是,標註成為了規範的一部分,但卻沒有經受過考驗。
- 在大多數情況下,文檔字符串已經包括了類型規範,比函數標註更清晰。在其余的情況下,一旦標註被移除,文檔字符串應該被完善。
- 觀察到的函數標註太標新立異了,相關的系統不能一致的處理自動類型檢查和參數驗證。離開這些標註的代碼以後很難做出更改,使自動化工具可以支持。
Python PEP8代碼規範_20180614