PEP: 8
標題: Python編碼風格指南
作者:
Guido van Rossum guido@python.org,
Barry Warsaw barry@python.org,
Nick Coghlan ncoghlan@gmail.com
狀態(tài): 生效
介紹
本文檔所提供的編碼規(guī)范,適用于主要的Python發(fā)行版中組成標準庫的Python代碼。請參閱PEP關于Python的C實現的C編碼風格指南的描述 [[[1]].
本文檔和PEP257(文檔字符串規(guī)范)改編自Guido的《Python Style Guide》一文,用Barry的風格指南[[2]]做一些補充。
這篇風格指南隨著時間的推移而逐漸演變,隨著語言本身的變化,過去的約定已經過時了并確定了更多新的約定。
許多項目都有自己的編碼風格指南。如果有任何沖突,優(yōu)先使用該項目特定的指南。
令人討厭的小人物身上愚蠢的一致性
Guido的一個重要的見解是,代碼閱讀的次數比編寫的次數多。這里提供的指南旨在提高代碼的可讀性,并使各種不同的Python代碼一致。如PEP20所說,“易讀性非常重要”。
風格指南是關于一致性的。與本風格指南一致很重要。項目中的一致性更重要。一個模塊或功能中的一致性最重要。
最重要的是:知道何時會不一致——有時風格指南就不適用了。懷疑時,作出你最佳的判斷??纯雌渌睦?,并決定什么是最好的。不要猶豫,盡管發(fā)問!
特別地:不要只為遵從這個PEP而打破向后兼容性!
忽視既定指南的一些其他的好理由:
當應用指南會降低代碼的可讀性,即使對于那些習慣遵照這個PEP來閱讀代碼的人來說。
與周圍的代碼保持一致也會破壞它(可能是歷史原因)——雖然這也是收拾別人爛攤子的好機會(在真正的XP風格中)。
因為問題代碼先于指南,又沒有其它的修改理由。
代碼需要兼容老版本,本風格指南不建議使用的Python特性。
代碼布局
縮進
每級縮進使用4個空格。
連續(xù)行應該對齊折疊元素,無論是垂直的Python的隱式行連接圓括號內的,中括號內的,大括號內的,還是使用懸掛縮進[[3]]。使用懸掛縮進應注意以下幾點;
風格良好:
# 與分界符對齊
foo = long_function_name(var_one, var_two,
var_three, var_four)
# 使用更多的縮進以區(qū)別于其他
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)
# 當無法區(qū)分所進食,需要進一步縮進
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
對于連續(xù)行,4個空格規(guī)則是可選的。
可選的:
# Hanging indents *may* be indented to other than 4 spaces.
foo = long_function_name(
var_one, var_two,
var_three, var_four)
多行if語句:
if語句條件塊足夠長時需要編寫多行,值得注意的是兩個字符組成的關鍵字(例如if),加上一個空格,加上開括號為多行條件的后續(xù)行創(chuàng)建一個4個空格的縮進。這可以給嵌入if內的縮進語句產生視覺沖突,這也自然被縮進4個空格。這個PEP沒有明確如何(是否)進一步區(qū)分條件行和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',
)
制表符還是空格?
空格是縮進方法的首選。
制表符僅用于與已經用制表符做縮進的代碼保持一致。
Python3不允許混用制表符和空格來縮進。
Python2代碼混用制表符和空格縮進,將被轉化為只使用空格。
調用Python2命令行解釋器時使用-t選項,可對代碼中非法混用制表符和空格發(fā)出警告。當使用-tt選項,警告將變成錯誤。這些選項是高度推薦的!
行的最大長度
限制所有行最多79個字符。
下垂的長塊結構限制為更少的文本(文檔字符串或注釋),行的長度應該限制在72個字符。
限制編輯器窗口寬度使得并排打開多個文件成為可能,并且使用代碼審查工具顯示相鄰列的兩個版本工作正常。
絕大多數工具的默認折疊會破壞代碼的可視化結構,使其更難以理解。編輯器中的窗口寬度設置為80個字符。即使該工具將在最后一列中標記字形。一些基于網絡的工具可能不會提供動態(tài)的自動換行。
有些團隊強烈喜歡較長的行長度。對于代碼維護完全或主要由一個團隊的,可以在這個問題上達成協(xié)議,象征性的將行長度從80個字符增加到100個字符(有效地增加最大長度到99個字符)也是可以的,提供注釋和文檔字符串仍是72個字符。
Python標準庫采取保守做法,要求行限制到79個字符(文檔字符串/注釋到72個字符)。
折疊長行的首選方法是在小括號,中括號,大括號中使用Python隱式換行。長行可以在表達式外面使用小括號來變成多行。連續(xù)行使用反斜杠更好。
反斜杠有時可能仍然是合適的。例如,長的多行的with語句不能用隱式續(xù)行,可以用反斜杠:
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語句。
確保適當的連續(xù)行縮進。
換行應該在二元操作符的前面還是后面?
過去二十年我們都是推薦放在二元操作符的后面。但是這種做法會以兩種方式傷害可讀性:多個二元操作符在屏幕上不在一列,另外如果你想知道對一個被操作的對象做了什么操作,需要向上找一行。這導致你的眼睛不得不上下往返很多次才能搞清楚哪個數字是被加的,哪個數字是被減的:
# 風格不良:operators sit far away from their operands
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
為了解決可讀性問題,數學家和印刷業(yè)者通常是在二元操作符之前換行的。Donald Knuth在他的《計算機與排版》系列文章中解釋了這個傳統(tǒng)規(guī)則:“雖然寫在一段話中的公式經常在二元操作符的后面換行,但是單獨展示的公式通常是在二元操作符的前面換行?!?/p>
出于遵循數學傳統(tǒng),所以我們這樣寫這段代碼將更加可讀:
# 好的做法:很容易看出二元操作符和被操作對象的關系
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
Python中,允許在二元操作符前或后換行,只要保持局部的一致性。新代碼建議遵循Knuth的風格。
空行
頂級函數和類的定義之間有兩行空行。
類內部的函數定義之間有一行空行。
額外的空行用來(謹慎地)分離相關的功能組。相關的行(例如:一組虛擬實現)之間不使用空行。
在函數中謹慎地使用空行來表示邏輯部分。
Python接受control-L(即^L)換頁符作為空白符;許多工具把這些字符作為分頁符,所以你可以使用它們?yōu)槲募械南嚓P部分分頁。注意,一些編輯器和基于Web的代碼查看器可能不能識別control-L是換頁,將顯示另外的字形。
源文件編碼
Python核心發(fā)布中的代碼應該始終使用UTF-8(或Python2中用ASCII)。
文件使用ASCII(Python2中)或UTF-8(Python3中)不應有編碼聲明。
在標準庫中,非默認編碼僅用于測試目的或注釋或文檔字符串需要提及包含非ASCII字符的作者名;否則,使用\x,\u,\U,或\N是字符串中包含非ASCII數據的首先方式。
Python3.0及以上版本,為標準庫(參見PEP 3131)規(guī)定以下策略:Python標準庫中的所有標識符必須使用ASCII標識符,在可行的地方使用英文單詞(在很多例子中,使用非英文的縮寫和專業(yè)術語)。另外,字符串和注釋必須用ASCII。僅有的例外是(a)測試非ASCII的特點,(b)測試作者名。不是基于拉丁字母表的作者名必須提供一個他們名字的拉丁字母表的音譯。
開源項目面向全球,鼓勵采用統(tǒng)一策略。
導入
-
導入通常是單獨一行,例如:
風格良好:
import os
import sys
風格不良:
風格不良: import sys, os
這樣也是可以的:
from subprocess import Popen, PIPE
-
導入常常位于文件頂部,在模塊注釋和字符串文檔之后,在模塊的全局變量和常量之前。
導入應該按照以下順序分組:
- 標準庫導入
- 相關的第三方導入
- 特定的本地應用/庫導入
在每個導入組之間放一行空行。
把任何相關all規(guī)范放在導入之后。
推薦絕對導入,因為它們更易讀,并且如果導入系統(tǒng)配置的不正確(例如當包中的一個目錄結束于sys.path)它們有更好的表現(至少給出更好的錯誤信息):
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example
明確的相對導入可以用來接受替代絕對導入,特別是處理復雜包布局時,絕對導入過于冗長。
from . import sibling
from .sibling import example
標準庫代碼應該避免復雜包布局并使用絕對導入。
隱式的相對導入應該永遠不被使用,并且在Python3中已經移除。
- 從一個包含類的模塊中導入類時,通常下面這樣是好的寫法:
from myclass import MyClass
from foo.bar.yourclass import YourClass
如果這種寫法導致本地名字沖突,那么就這樣寫:
import myclass
import foo.bar.yourclass
并使用“myclass.MyClass”和“foo.bar.yourclass.YourClass”來訪問。
-
避免使用通配符導入(
from <模塊名> import *),因為它們使哪些名字出現在命名空間變得不清楚,這混淆了讀者和許多自動化工具。通配符導入有一種合理的使用情況,重新發(fā)布一個內部接口作為一個公共API的一部分(例如,重寫一個純Python實現的接口,該接口定義從一個可選的加速器模塊并且哪些定義將被重寫提前并不知道)用這種方式重新命名,下面的有關公共和內部接口的指南仍適用。
模塊級別的內置屬性
模塊級別的內置屬性(名字有前后雙下劃線的),例如__all__, __author__, __version__,應該放置在模塊的文檔字符串后,任意import語句之前,from __future__導入除外。Python強制要求from __future__導入必須在任何代碼之前,只能在模塊級文檔字符串之后。
"""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不建議如此。建議選擇一條規(guī)則并堅持下去。當一個字符串包含單引號字符或雙引號字符時,使用另一種字符串引號來避免字符串中使用反斜杠。這提高可讀性。
三引號字符串,與PEP 257 文檔字符串規(guī)范一致總是使用雙引號字符。
表達式和語句中的空格
無法忍受的
以下情況避免使用多余的空格:
- 緊挨著小括號,中括號或大括號。
風格良好: 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
-
在切片中冒號像一個二元操作符,冒號兩側的有相等數量空格(把它看作最低優(yōu)先級的操作符)。在一個擴展切片中,兩個冒號必須有相等數量的空格。例外:當一個切片參數被省略時,該空格被省略。
風格良好:
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
其它建議
始終避免行尾空白。因為它們通常不可見,容易導致困惑:如果\后面跟了一個空格,它就不是一個有效的續(xù)行符了。很多編輯器不保存行尾空白,CPython項目中也設置了commit前檢查以拒絕行尾空白的存在。
始終在這些二元操作符的兩邊放置一個空格:賦值(
=),增強賦值(+=,-=等),比較(==,<,>,!=,<>,<=,>=,in,not in,is,is not),布爾(and,or,not)。-
如果使用了不同優(yōu)先級的操作符,在低優(yōu)先級操作符周圍增加空格(一個或多個)。不要使用多于一個空格,二元運算符兩側空格數量相等。
風格良好:
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) -
帶注解的函數使用正常的冒號規(guī)則,并且在
->兩側增加一個空格:風格良好:
def munge(input: AnyStr): ... def munge() -> AnyStr: ...風格不良:
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()
什么時候使用尾部逗號?
尾部逗號通常都是可選的,除了一些強制的場景,比如元組在只有一個元素的時候需要一個尾部逗號。為了代碼更加清晰,元組只有一個元素時請務必用括號括起來(語法上沒有強制要求):
風格良好:
FILES = ('setup.cfg',)
技術上可行,但看起來令人困惑:
FILES = 'setup.cfg',
當尾部逗號不是必須時,如果你用了版本控制系統(tǒng)那么它將很有用。當列表元素、參數、導入項未來可能不斷增加時,留一個尾部逗號是一個很好的選擇。通常的用法是(比如列表)每個元素獨占一行,然后尾部都有逗號,在最后一個元素的下一行寫閉標簽。如果你的數據結構都是寫在同一行的,就沒有必要保留尾部逗號了。
風格良好:
FILES = [
'setup.cfg',
'tox.ini',
]
initialize(FILES,
error=True,
)
風格不良:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)
注釋
同代碼相矛盾的注釋比沒有注釋更差。當代碼修改時,始終優(yōu)先更新注釋!
注釋應該是完整的句子。如果注釋是一個短語或句子,它的第一個單詞的首字母應該大寫,除非它是一個以小寫字母開頭的標識符(不更改標識符的情況下?。?/p>
如果注釋很短,末尾可以不加句號。注釋塊通常由一個或多個段落組成,這些段落由完整的句子組成,并且每個句子都應該以句號結尾。
在句尾的句號后邊使用兩個空格。
寫英語注釋時,遵循斷詞和空格。
非英語國家的Python程序員:請用英語書寫注釋,除非你120%的確定,所有看你代碼的人都和你說一樣的語言。
注釋塊
注釋塊通常適用于一些(或全部)緊跟其后的代碼,并且那些代碼應使用相同級別的縮進。注釋塊的每行以一個#和一個空格開始(除非注釋里面的文本有縮進)。
注釋塊內的段落之間由僅包含#的行隔開。
行內注釋
謹慎地使用行內注釋。
行內注釋就是注釋和代碼在同一行,它與代碼之間至少用兩個空格隔開。并且它以#和一個空格開始。
如果行內注釋指出的是顯而易見,那么它就是不必要的。 不要這樣做:
x = x + 1 # 遞增x
但有時,這樣是有用的:
x = x + 1 # Compensate for border
文檔字符串
編寫好的文檔字符串(即“代碼”)約定在PEP 257中是永存的。
為所有公共模塊,函數,類和方法書寫文檔字符串。對非公開的方法書寫文檔字符串是沒有必要的,但應該寫注釋描述這個方法是做什么的。這些注釋應該寫在
def行后面。PEP 257描述了好的文檔字符串約定。最重要的是,多行文檔字符串以一行
"""結束,例如:
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
- 對于只有一行的文檔字符串,
"""在同一行上。
命名規(guī)范
Python庫的命名規(guī)范有點兒混亂,所以我們不會將他們變得完全一致——不過,這是目前推薦的命名標準。新模塊和包(包括第三方框架)應該按這些標準書寫,但對有不同的風格的已有庫,保持內部一致性是首選。
根本原則
用戶可見的API的公開部分的名稱,應該遵循反映用法而不是實現的約定。
描述:命名風格
有很多不同的命名風格。它有助于識別使用了什么樣的命名風格,這獨立于他們的作用。
下面的命名風格是最常見的:
b(single lowercase letter)B(single uppercase letter)lowercaselower_case_with_underscoresUPPERCASEUPPER_CASE_WITH_UNDERSCORES-
CapitalizedWords(或CapWords,或駝峰命名法——因其字母看起來高低不平而得名[[4]])。這有時也被稱為StudlyCaps.注意:當CapWords中使用縮寫,大寫所有的縮寫字母。因此HTTPServerError優(yōu)于HttpServerError。
mixedCase(與首字母大寫字符串不同的是它以小寫字母開頭)Capitalized_Words_With_Underscores(丑!)
還有一種風格,使用簡短獨特的前綴組織相關的名字在一起。Python中很少這樣用,提一下是為了文檔的完整性。例如,os.stat()函數返回一個元祖,它的元素名字通常類似st_mode,st_size,st_mtime等等這樣。(這樣做是為了強調與POSIX系統(tǒng)調用結構體一致,這有助于程序員熟悉這些。)
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__. 不要創(chuàng)造這樣的名字,僅使用官方記錄的。
規(guī)定:命名約定
避免采用的名字
不要使用字符l(小寫字母el),O(大寫字母oh)或I(大寫字母eye)作為單字符變量名。
在某些字體中,這些字符與數字1和0是沒有區(qū)別的。當想使用l時,用L代替。
ASCII兼容性
標準庫中使用的標識符必須與ASCII兼容,詳見PEP 3131。
包名和模塊名
模塊名應該短,所有的字母小寫??梢栽谀K名中使用下劃線來提高可讀性。Python包名也應該短,所有的字母小寫,不鼓勵使用下劃線。
當一個C或C++書寫的擴展模塊,伴隨Python模塊來提供了一個更高層次(例如更面向對象)的接口時,C/C++模塊名有一個前導下劃線(如_socket)。
類名
類名通常使用首字母大寫字符串的規(guī)則。
函數的命名規(guī)則 主要用來可調用的。
在接口被記錄并且主要用作調用的情況下,用函數的命名規(guī)則來代替類名的命名規(guī)則。
注意內置名有一個單獨的規(guī)則:大多數的內置名是一個單詞(或兩個單詞一起),首字母大寫字符串的規(guī)則僅用于異常名和內置常量。
類型變量名稱
類型變量名稱應該首字母大寫,并且盡量短,比如:T, AnyStr, Num。對于協(xié)變量和有協(xié)變行為的變量,建議添加后綴__co或者__contra:
from typing import TypeVar
VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)
異常名
因為異常應該是類,所以類的命名規(guī)則在這里也同樣適用。然而,異常名(如果這個異常確實是一個錯誤)應該使用后綴“Error”。
全局變量名
希望這些變量是在一個模塊內使用。)這些規(guī)則和那些有關函數的規(guī)則是相同的。
模塊設計為通過from M import *來使用,應使用all機制防止導出全局變量,或使用加前綴的舊規(guī)則,為全局變量加下劃線(可能你想表明這些全局變量是“非公開模塊”)。
函數和變量名
函數名應該是小寫字母,必要時單詞用下劃線分開以提高可讀性。
變量名遵循和函數名相同的規(guī)定。
混合大小寫僅用于這種風格已經占主導地位的上下文(例如threading.py),以保持向后兼容性。
函數和方法參數
使用self做實例化方法的第一個參數。
使用cls做類方法的第一個參數。
如果函數的參數名與保留關鍵字沖突,最好是為參數名添加一個后置下劃線而不是使用縮寫或拼寫錯誤。
因此class_ 比clss好。(也許使用同義詞來避免更好。)。
方法名和實例變量
采用函數命名規(guī)則:小寫字母,必要時單詞用下劃線分開以提高可讀性。
僅為非公開的方法和實例變量使用一個前導下劃線。
為了避免和子類命名沖突,使用兩個前導下劃線調用Python的名稱改編規(guī)則。
Python用類名改編這些名字:如果類Foo有一個屬性名為__a,通過Foo.__a不能訪問。(可以通過調用Foo._Foo__a來訪問。)通常,兩個前導下劃線僅用來避免與子類的屬性名沖突。
注意:關于__names的使用存在一些爭論(見下文)。
常量
常量通常定義于模塊級別并且所有的字母都是大寫,單詞用下劃線分開。例如MAX_OVERFLOW和TOTAL。
繼承的設計
確定類的方法和實例變量(統(tǒng)稱為:“屬性”)是否公開。如果有疑問,選擇非公開;之后把其變成公開比把一個公開屬性改成非公開要容易。
公開屬性是那些你期望與你的類不相關的客戶使用的,根據你的承諾來避免向后不兼容的變更。非公開屬性是那些不打算被第三方使用的;你不保證非公開屬性不會改變甚至被刪除。
非公共屬性是那些不打算被第三方使用的,你不能保證非公開的屬性不會改變甚至被刪除。
此處沒有使用術語“private”,因為Python中沒有真正私有的屬性(沒有通常的不必要的工作)。
屬性的另一個類別是“API子集”的一部分(在其它語言常被稱為“protected”)。某些類被設計為基類,要么擴展,要么修改某些方面的類的行為。在設計這樣的類的時候,要注意明確哪些屬性是公開的,哪些是API子類的一部分,哪些是真正只在你的基類中使用。
清楚這些之后,這是Python特色的指南:
公開屬性沒有前導下劃線。
-
如果公開屬性名和保留關鍵字沖突,給屬性名添加一個后置下劃線。這比縮寫或錯誤拼寫更可取。(然而,盡管有這樣的規(guī)定,對于任何類的變量或參數,特別是類方法的第一個參數,‘cls’是首選的拼寫方式)
注1:參見上面對類方法的參數名的建議。
-
對于簡單的公開數據屬性,最好只暴露屬性名,沒有復雜的訪問器/修改器方法。記住,Python為未來增強提供了一條簡單的途徑,你應該發(fā)現簡單的數據屬性需要增加功能行為。在這種情況下,使用屬性來隱藏簡單數據屬性訪問語法后面的功能實現。
注1:特性僅工作于新風格的類。
注2:盡量保持功能行為無副作用,盡管副作用如緩存通常是好的。
注3:計算開銷較大的操作避免使用特性,屬性注解使調用者相信訪問(相對)是廉價的。
-
如果確定你的類會被子類化,并有不想子類使用的屬性,考慮用兩個前導下劃線無后置下劃線來命名它們。這將調用Python的名稱改編算法,類名將被改編為屬性名。當子類不無意間包括相同的屬性名時,這有助于幫助避免屬性名沖突。
注1:注意改編名稱僅用于簡單類名,如果一個子類使用相同的類名和屬性名,仍然會有名字沖突。
注2:名稱改編會帶來一定的不便,如調試和
__getattr__()。然而,名稱改編算法有良好的文檔,也容易手工執(zhí)行。注3:不是每個人都喜歡名稱改編。嘗試平衡避免意外的名稱沖突和高級調用者的可能。
公共和內部接口
任何向后兼容性保證只適用于公共接口。因此,重要的是用戶能夠清楚地區(qū)分公開和內部接口。
文檔接口被認為是公開的,除非文檔明確聲明他們是臨時或內部接口,免除通常的向后兼容保證。所有非文檔化的接口應假定為內部接口。
為了更好的支持自省,模塊應該使用__all__屬性顯示聲明他們公開API的名字, __all__設置為一個空列表表示該模塊沒有公開API。
即使__all__設置的適當,內部接口(包,模塊,類,函數,屬性或者其它名字)仍應以一個前導下劃線作前綴。
一個接口被認為是內部接口,如果它包含任何命名空間(包,模塊,或類)被認為是內部的。
導入名被認為是實現細節(jié)。其它模塊必須不依賴間接訪問這個導入名,除非他們是一個明確的記錄包含模塊的API的一部分,例如os.path或包的__init__模塊,從子模塊暴露功能。
程序設計建議
-
編寫的代碼應該不損害其他方式的Python實現(PyPy,Jython,IronPython,Cython,Psyco等等)。
例如,不要依賴CPython的高效實現字符串連接的語句形式
a += b或a = a + b。這種優(yōu)化即使在CPython里也是脆弱的(它只適用于某些類型),并且在不使用引用計數的實現中它完全不存在。在庫的性能易受影響的部分,應使用''.join()形式。這將確保跨越不同實現的連接發(fā)生在線性時間。 -
與單值比如None比較使用
is或is not,不要用等號操作符。同樣,如果你真正的意思是
if x is not None,謹防編寫if x。例如,當測試一個默認是None的變量或參數是否被置成其它的值時。這個其它值可能是在布爾上下文為假的類型(例如容器) -
使用
is not操作符而不是not...is。雖然這兩個表達式的功能相同,前者更具有可讀性并且更優(yōu)。風格良好:
if foo is not None:風格不良:
if not foo is None: -
當實現有豐富的比較的排序操作時,最好實現所有六個操作符(
__eq__,__ne__,__lt__,__le__,__gt__,__ge__)而不是依靠其它代碼只能進行一個特定的比較。為了減少所涉及的工作量,
functools.total_ordering()裝飾器提供了一個工具來生成缺失的比較函數。PEP 207表明,Python假定自反性規(guī)則。因此,編譯器可以交換
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是保留那些捕捉幾乎總是錯的異常的。設計異常層次結構基于區(qū)別,代碼可能需要捕獲異常,而不是捕獲產生異常的位置。旨在以編程方式回答問題“出了什么問題?”,而不是只說“問題產生了”(參見PEP 3151對這節(jié)課學習內置異常層次結構的一個例子)
類的命名規(guī)則適用于此,只是當異常確實是錯誤的時候,需要在異常類名添加“Error”后綴。用于非本地的流控制或其他形式的信號的非錯誤的異常,不需要特殊的后綴。
-
適當使用異常鏈。Python3中,“raise X from Y”用來表明顯示替換而不失去原來追蹤到的信息。
當故意替換一個內部異常(Python 2中使用“raise X”而Python 3.3+中使用“raise X from None”),確保相關的細節(jié)被轉移到新的異常中(比如當轉換KeyError為AttributeError時保留屬性名,或在新的異常消息中嵌入原始異常的文本)。
-
Python 3中產生異常,使用raise ValueError('message')替換老的形式raise ValueError,'message'。后一種形式是不合法的Python 3語法。
目前使用的形式意味著當異常的參數很長或包含格式化字符傳時,多虧了小括號不必再使用續(xù)行符。
-
捕獲異常時,盡可能提及特定的異常,而不是使用空的except:子句。
try: import platform_specific_module except ImportError: platform_specific_module = None空的
except:子句將捕獲SystemExit和KeyboardInterrupt異常,這使得很難用Control-C來中斷程序,也會掩飾其它的問題。如果想捕獲會導致程序錯誤的所有異常,使用except Exception:(空異常相當于except BaseException:)一條好的經驗法則是限制使用空‘except’子句的兩種情況:
如果異常處理程序將打印或記錄跟蹤;至少用戶將會意識到有錯誤發(fā)生。
如果代碼需要做一些清理工作,但是隨后讓異常用
raise拋出。處理這種情況用try...finally更好。
-
當用一個名字綁定捕獲異常時,更喜歡Python2.6中添加的明確的名稱綁定語法。
try: process_data() except Exception as exc: raise DataProcessingFailedError(str(exc))這是Python3中唯一支持的語法,并避免與舊的基于逗號的語法有關的歧義問題。
捕獲操作系統(tǒng)異常時,更喜歡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 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) -
使用字符串方法代替string模塊。
字符串方法總是更快并且與unicode字符串使用相同的API。如果必須向后兼容Python2.0以前的版本,無視這個原則。
-
使用
''.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):注意,Python3中,unicode和basestring不再存在(只有str),并且字節(jié)對象不再是string(而是一個integers序列)
-
對于序列(字符串,列表,元組),利用空序列是false的事實。
風格良好: if not seq: if seq:風格不良: if len(seq): if not len(seq): 不要書寫依賴后置空格的字符串。這些后置空格在視覺上無法區(qū)分,并且有些編輯器(或最近,reindent.py)將去掉他們。
-
不要用
==來將布爾值與True或False進行比較。風格良好: if greeting: 風格不良: if greeting == True: Worse: if greeting is True:
函數注解
隨著PEP 484被接受,功能的風格規(guī)則注釋正在改變。
為了向前兼容,在Python 3中使用函數注釋代碼最好使用PEP 484語法。(上一節(jié)中有推薦的注解建議。)
在此PEP中已不甜使用之前的實驗性注解風格。
然而在stdlib外,我們還是建議使用PEP 484的規(guī)則。例如在構建大型第三方庫的時候,加上這些注解可以很好地提升可讀性。
在Python標準庫中,對于這類注解應持保守態(tài)度,但在新代碼和大型重構中這類注解是允許的。
-
對于對函數注解有不同用途的代碼,我們建議放置一下形式的注釋:
# type: ignore注釋寫在文件頂部;這會告訴類型檢查器忽略所有注釋。(在PEP 484中可以找到更細致的禁用類型檢查的方法。)
類似于提示器,類型檢查器是可選的獨立的工具。Python編譯器不能默認在解釋時出發(fā)任何消息也不能因此改變它的行為。
不想使用類型檢查器的用戶可以忽略它們。但是我們希望使用第三方庫的用戶可以使用檢查器。基于此,PEP 484建議在使用stub文件時:相較于.py文件,類型檢查器優(yōu)先讀取.pyi文件。Stub文件可以同庫一起發(fā)布,也可以單獨(在作者允許下)從代碼倉庫獲得。
對于需要向后兼容的代碼, 類型注解可以以注釋的形式添加。請參閱PEP 484相關部分。
變量注解
PEP 526引入了變量注釋。其風格建議與上述的函數注解類似:
模塊級、類和實例級、局部變量需要在冒號后加一個空格。
T冒號前不應有空格。
如果賦值有右邊部分,那么等號兩邊應該都留有一個空格。
-
風格良好:
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 雖然在Python 3.6中已接受PEP 526,但是變量注釋語法是所有Python版本中stub文件的首選語法(詳情參看PEP 484)。
參考文獻
[1] PEP 7, Style Guide for C Code, van Rossum
[2] Barry's GNU Mailman style guide
http://barry.warsaw.us/software/STYLEGUIDE.txt
[3] Donald Knuth's The TeXBook, pages 195 and 196.
[4] http://www.wikipedia.com/wiki/CamelCase
[5] Typeshed repo
https://github.com/python/typeshed
[6] Suggested syntax for Python 2.7 and straddling code
https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code
版權
該文件已付諸公示。