更新時間:2016/5/13
介紹
本文檔所提供的編碼規范,適用于主要的Python發行版中組成標準庫的Python代碼。請參閱PEP關于Python的C實現的C編碼風格指南的描述。
本文檔和PEP257(文檔字符串規范)改編自Guido的《Python Style Guide》一文,并從《Barry's style guide》添加了部分內容作為補充。
這篇風格指南隨著時間的推移而逐漸演變,隨著語言本身的變化,一些過去的約定已經過時,并確定了更多新的約定。
許多項目都有自己的編碼風格指南。如果有任何沖突,優先使用該項目特定的指南。
愚蠢地堅持一致性是無知的妖怪
Guido的一個重要的見解是,代碼閱讀的次數比編寫的次數多。這里提供的指南旨在提高代碼的可讀性,并使各種不同的Python代碼一致。如PEP20所說,“易讀性非常重要”。
風格指南是關于一致性的。與本風格指南一致很重要。項目中的一致性更重要。一個模塊或功能中的一致性最重要。
最重要的是:知道何時會不一致——有時風格指南就不適用了。懷疑時,作出你最佳的判斷。看看其他的例子,并決定什么是最好的。不要猶豫,盡管發問!
特別地:不要只為遵從這個PEP而打破向后兼容性!
可以忽略部分風格指南的好理由,不要只為遵從這個PEP而打破向后兼容性!
忽視既定指南的一些其他的好理由:
- 當應用指南會降低代碼的可讀性,即使對于那些習慣遵照這個PEP來閱讀代碼的人來說。
- 與周圍的代碼保持一致也會破壞它(可能是歷史原因)——雖然這也是收拾別人爛攤子的好機會(在真正的XP風格中)。
- 因為問題代碼先于指南,又沒有其它的修改理由。
- 代碼需要兼容老版本,本風格指南不建議使用的Python特性。
代碼布局
縮進
每級縮進用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)
# 懸掛時,應該增加一級縮進
foo = long_function_name(
var_one, var_two,
var_three, var_four)
# 懸掛不一定要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 3不允許混合使用制表符和空格來縮進。
Python 2的代碼中混合使用制表符和空格的縮進,應該轉化為完全使用空格。
調用python命令行解釋器時使用-t選項,可對代碼中不合法得混合制表符和空格發出警告(warnings)。使用-tt時警告(warnings)將變成錯誤(errors).這些選項是被高度推薦的.
最大行長度
限制所有行最多79個字符。
下垂的長塊結構限制為更少的文本(文檔字符串或注釋),行的長度應該限制在72個字符。
限制編輯器窗口寬度使得并排打開多個文件成為可能,并且使用代碼審查工具顯示相鄰列的兩個版本工作正常。
絕大多數工具的默認折疊會破壞代碼的可視化結構,使其更難以理解。編輯器中的窗口寬度設置為80個字符。即使該工具將在最后一列中標記字形。一些基于網絡的工具可能不會提供動態的自動換行。
有些團隊強烈喜歡較長的行長度。對于代碼維護完全或主要由一個團隊的,可以在這個問題上達成協議,象征性的將行長度從80個字符增加到100個字符(有效地增加最大長度到99個字符)也是可以的,提供注釋和文檔字符串仍是72個字符。
Python標準庫采取保守做法,要求行限制到79個字符(文檔字符串/注釋到72個字符)。
折疊長行的首選方法是使用Pyhon支持的圓括號,方括號(brackets)和花括號(braces)內的行延續。長行可以在表達式外面使用小括號來變成多行。連續行使用反斜杠更好。
反斜杠有時可能仍然是合適的。例如,長的多行的with語句不能用隱式續行,可以用反斜杠:
# 反斜杠有時可能仍然是合適的。例如,長的多行的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())
另一種類似的情況是在assert
語句中
確認恰當地縮進了延續的行
換行應該在二元操作符前面還是后面
幾十年來推薦的風格是在二元操作符后換行。但這會通過兩種方式影響可讀性:操作符往往分散在屏幕的不同的列上,并且每個操作符都被移動到遠離其操作數。在這里,眼睛要做額外的工作看清哪些數是添加或減去:
# 壞的: 操作符遠離它們的操作數
income = (gross_wages +
taxable_interest +
(dividends - qualified_dividends) -
ira_deduction -
student_loan_interest)
為了解決這個可讀性問題,數學家和出版商遵循相反的約定。
Donald Knuth在他的《電腦和排版》系列中解釋了傳統規則:“盡管后一段總是打破內公式二進制操作和關系,顯示公式總是在二進制操作前換行”。
以下的數學運算傳統通常可以使得結果更加具有可讀性
# 好的: 易于匹配操作數和操作符
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
#二元運算符首選的換行位置在操作符后面
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)
在Python代碼中,允許打破之前或之后一個二元運算符的規則,只要與當前慣例是一致的。為新代碼Knuth的風格。
空行
用兩行空行分割頂層函數和類的定義.
類內方法的定義用單個空行分割.
額外的空行可被用于(保守的(sparingly))分割一組相關函數(groups of related functions).
在一組相關的單句中間可以省略空行.(例如.一組啞元(a set of dummy implementations)).
在函數中使用空行時,請謹慎的用于表示一個邏輯段落(indicate logical sections).
Python接受contol-L(即^L)換頁符作為空格;Emacs(和一些打印工具) 視這個字符為頁面分割符,因此在你的文件中,可以用他們來為相關片段(sections)分頁。注意,一些編輯器和基于Web的代碼查看器可能不能識別control-L是換頁,將顯示另外的字形。
源文件編碼
Python核心發布中的代碼應該始終使用UTF-8(或Python2中用ASCII)。
文件使用ASCII(Python2中)或UTF-8(Python3中)不應有編碼聲明。
在標準庫中,非默認編碼僅用于測試目的或注釋或文檔字符串需要提及包含非ASCII字符的作者名;否則,使用\x,\u,\U,或\N是字符串中包含非ASCII數據的首先方式。
Python3.0及以上版本,為標準庫(參見PEP 3131)規定以下策略:Python標準庫中的所有標識符必須使用ASCII標識符,在可行的地方使用英文單詞(在很多例子中,使用非英文的縮寫和專業術語)。
另外,字符串和注釋必須用ASCII。僅有的例外是(a)測試非ASCII的特點,(b)測試作者名。不是基于拉丁字母表的作者名必須提供一個他們名字的拉丁字母表的音譯。
開源項目面向全球,鼓勵采用統一策略。
導入
# 通常應該在單獨的行中導入(Imports)
import os
import sys
# 這樣也是可以的
from subprocess import Popen, PIPE
Imports 通常被放置在文件的頂部,僅在模塊注釋和文檔字符串之后,在模塊的全局變量和常量之前.
Imports 應該有順序地成組安放.
- 標準庫的導入(Imports )
- 相關的第三方導入(即,所有的email包在隨后導入)
- 特定的本地應用/庫導入(imports)
你應該在每組導入之間放置一個空行.
把任何相關all規范放在導入之后。
推薦絕對導入,因為它們更易讀,并且如果導入系統配置的不正確(例如當包中的一個目錄在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 *),因為這樣就不清楚哪些名字出現在命名空間,這會讓讀者和許多自動化工具困惑。
通配符導入有一種合理的使用情況,重新發布一個內部接口作為一個公共API的一部分(例如,重寫一個純Python實現的接口,該接口定義從一個可選的加速器模塊并且哪些定義將被重寫提前并不知道)。
用這種方式重新命名,下面的有關公共和內部接口的指南仍適用。
字符串引號
Python中,單引號字符串和雙引號字符串是一樣的,本PEP不建議如此,建議選擇一個并堅持下去。
當一個字符串包含單引號字符或雙引號字符時,使用另一種字符串引號來避免字符串中使用反斜杠。這可以提高可讀性。
三引號字符串,總是使用雙引號字符,與PEP 257 文檔字符串規范保持一致。
表達式和語句中的空格
Guido不喜歡在以下地方出現空格
以下情況避免使用多余的空格
# 緊挨著圓括號,方括號和花括號的
spam(ham[1], {eggs: 2})
# 緊貼在逗號,分號或冒號前的
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]
# 緊貼著函數調用的參數列表前開式括號(open parenthesis )的
spam(1)
# 緊貼在索引或切片(slicing?下標?)開始的開式括號前的
dct['key'] = lst[index]
# 在賦值(或其它)運算符周圍,不要為了與其他的賦值(或其它)操作符對齊,使用不止一個空格。
x = 1
y = 2
long_variable = 3
其它建議
避免尾隨空格。
因為它通常是無形的,它可能會讓人很困惑:如一個反斜杠,后跟一個空格和一個換行符不算作一行延續標記。有些編輯器不保護它,并且許多項目(如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)
# 不要在用于指定關鍵字參數或默認參數值的'='號周圍使用空格,例如:
def complex(real, imag=0.0):
return magic(r=real, i=imag)
# 函數注釋應該對冒號使用正常規則,并且,如果存在 -> ,兩邊應該有空格
def munge(input: AnyStr): ...
def munge() -> AnyStr: ...
# 結合一個參數注釋和默認值時,在 = 符號兩邊使用空格(僅僅當那些參數同時有注釋和一個默認值時)。
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
# 不要將多條語句寫在同一行上.
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()
注釋
同代碼不一致的注釋比沒注釋更差.當代碼修改時,始終優先更新注釋!
注釋應該是完整的句子. 如果注釋是一個短語或句子,首字母應該大寫, 除非他是一個以小寫字母開頭的標識符(永遠不要修改標識符的大小寫).
如果注釋很短,最好省略末尾的句號。注釋塊通常由一個或多個由完整句子構成的段落組成,每個句子都應該以句號結尾.
你應該在句末的句號后使用兩個空格,以便使Emacs的斷行和填充工作協調一致 (譯按:應該說是使這兩種功能正常工作,". "給出了文檔結構的提示).
用英語書寫時,斷詞和空格是可用的.
非英語國家的Python程序員:請用英語書寫你的注釋,除非你120%的確信 這些代碼不會被不懂你的語言的人閱讀.
"""
"""
我就是堅持全部使用中文來注釋,真正要發布腳本工具時,再想E文的;
開發時每一瞬間都要用在思量中,堅決不用在E文語法,單詞的回憶中!
-- ZoomQUiet約定使用統一的文檔化注釋格式有利于良好習慣和團隊建議!文檔化開發注釋規范
注釋塊
注釋塊通常應用于跟隨著一些(或者全部)代碼并和這些代碼有著相同的縮進層次. 注釋塊中每行以'#'和一個空格開始(除非他是注釋內的縮進文本).
注釋塊內的段落以僅含單個'#'的行分割.
行內注釋
行內注釋應該謹慎使用.
一個行內注釋是和語句在同一行的注釋.行內注釋應該至少用兩個空格和語句分開. 它們應該以'#'和單個空格開始.
如果行內注釋指出的是顯而易見的,那么它就是不必要的。
# 不要這樣做:
x = x + 1 # 增加 x
# 但有時,這樣是有用的:
x = x + 1 # 補償border
文檔字符串
應該一直遵守在PEP 257中編寫好的文檔字符串(又名"docstrings")的約定,
"""
"""
Documentation Strings-- 文檔化字符 ;
為配合 pydoc;epydoc,Doxygen等等文檔化工具的使用,類似于MoinMoin 語法,約定一些字符,
以便自動提取轉化為有意義的文檔章節等等文章元素!
-- Zoomq
"""
為所有公共模塊,函數,類和方法編寫文檔字符串.文檔字符串對非公開的方法不是必要的,但你應該有一個描述這個方法做什么的注釋.這個注釋應該在"def"這行后.
PEP 257 描述了好的文檔字符串的約定.一定注意,多行文檔字符串結尾的""" 應該單獨成行,例如:
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
對單行的文檔字符串,請保持結尾的"""在同一行.
版本注記
如果你要將RCS或CVS的雜項(crud)包含在你的源文件中,按如下做.
__version__ = "$Revision$"
# $Source: E:/cvsroot/python_doc/pep8.txt,v $
這個行應該包含在模塊的文檔字符串之后,所有代碼之前,上下用一個空行分割.
對于CVS的服務器工作標記更應該在代碼段中明確出它的使用
如:在文檔的最開始的版權聲明后應加入如下版本標記:
# 文件:$id$
# 版本: $Revision$
這樣的標記在提交給配置管理服務器后,會自動適配成為相應的字符串,如:
# 文件:$Id: ussp.py,v 1.22 2004/07/21 04:47:41 hd Exp $
# 版本: $Revision: 1.4 $
----HD
命名約定
Python庫的命名約定有點混亂,所以我們將永遠不能使之變得完全一致--- 不過還是有公認的命名規范的.
新的模塊和包(包括第三方的框架)必須符合這些標準,但對已有的庫存在不同風格的, 保持內部的一致性是首選的.
根本原則
用戶可見的API的公開部分的名稱,應該體現用法而不是實現。
描述:命名風格
有許多不同的命名風格.以下的有助于辨認正在使用的命名風格,獨立于它們的作用.
以下的命名風格是眾所周知的:
- b (單個小寫字母)
- B (單個大寫字母)
- 小寫串 如:getname
- 帶下劃的小寫串 如:_getname,lower_case_with_underscores
- 大寫串 如:GETNAME
- 帶下劃的大寫串 如:_GETNAME,UPPER_CASE_WITH_UNDERSCORES
- CapitalizedWords(首字母大寫單詞串) (或 CapWords, CamelCase/駝峰命名法 -- 這樣命名是由于它的字母錯落有致的樣子而來的.這有時也被當作StudlyCaps. 如:GetName
- 注意:當CapWords中使用縮寫,大寫所有的縮寫字母。因此HTTPServerError優于HttpServerError。
- mixedCase (混合大小寫串)(與首字母大寫串不同之處在于第一個字符是小寫如:getName)
- Capitalized_Words_With_Underscores(帶下劃線的首字母大寫串) (丑陋!)
還有一種使用特別前綴的風格,用于將相關的名字分成組。Python中很少這樣用,但是出于完整性要提一下.
例如,os.stat()函數返回一個元祖,它的元素名字通常類似st_mode,st_size,st_mtime等等這樣。(這樣做是為了強調與POSIX系統調用結構體一致,這有助于程序員熟悉這些。)
X11庫的所有的公開函數以X開頭。Python中,這種風格通常認為是不必要的,因為屬性名和函數名以對象名作前綴,而函數名以模塊名作前綴。
另外,以下用下劃線作前導或結尾的特殊形式是被公認的(一般可以與任何約定相結合):
_single_leading_underscore(以一個下劃線作前導): 弱的"內部使用(internal use)"標志.
(例如,"from M import *"不會導入以下劃線開頭的對象).
single_trailing_underscore_(以一個下劃線結尾): 用于避免與Python關鍵詞的沖突,例如.
"Tkinter.Toplevel(master, class_='ClassName')".
__double_leading_underscore(雙下劃線): 從Python 1.4起為類私有名.調用時名稱改變(類FooBar中,__boo變成 _FooBar__boo;見下文)。
__double_leading_and_trailing_underscore__: 特殊的(magic) 對象或屬性,存在于用戶控制的(user-controlled)名字空間.
例如:__init__, __import__ 或 __file__.
有時它們被用戶定義, 用于觸發某個特殊行為(magic behavior)(例如:運算符重載);
有時被構造器(infrastructure)插入,以便自己使用或為了調試. 因此,在未來的版本中,構造器(松散得定義為Python解釋器和標準庫) 可能打算建立自己的魔法屬性列表,用戶代碼通常應該限制將這種約定作為己用. 欲成為構造器的一部分的用戶代碼可以在下滑線中結合使用短前綴,例如. __bobo_magic_attr__.
說明:命名約定
a. 應避免的名字
永遠不要用字符'l'(小寫字母el(就是讀音,下同)),'O'(大寫字母oh),或'I'(大寫字母eye)作為單字符的變量名.
在某些字體中,這些字符不能與數字1和0分開.當想要使用'l'時,用'L'代替它.
b. 包和模塊名
模塊應該是簡短的,全小寫的名字. 可以在模塊名中使用下劃線來提高可讀性.
Python包名也應該是簡短的,全小寫的名字,不鼓勵使用下劃線。
當一個用C或C++寫的擴展模塊有一個伴隨的Python模塊,這個Python模塊提供了一個更高層(例如,更面向對象)的接口時,C/C++模塊有一個前導下劃線(如:_socket)
c. 類名
幾乎沒有例外,類名總是使用首字母大寫單詞串(CapWords)的約定.
d. 異常名
因為異常應該是一個類,類的規范也適用這里。無論怎樣,你應該在異常名中使用后綴"Error"(如果實際是一個錯誤).
似乎內建(擴展)的模塊使用"error"(例如:os.error), 而Python模塊通常用"Error" (例如: xdrlib.Error).
e. 全局變量名
(讓我們希望這些變量打算只被用于模塊內部) 這些約定與那些用于函數的約定差不多.
被設計可以通過"from M import *"來使用的那些模塊, 應該使用all機制防止導出全局變量,或使用加前綴的舊規則,在那些不想被導入的全局變量(還有內部函數和類)前加一個下劃線.
f. 函數名
函數名應該為小寫,可以將單詞用下劃線分開以增加可讀性.
mixedCase混合大小寫 僅被允許用于這種風格已經占優勢的上下文(如: threading.py) 以便保持向后兼容.
g. 函數和方法參數
總是使用self做實例方法的第一個參數。
總是使用cls做類方法的第一個參數。
如果一個函數的參數名與保留關鍵字沖突,最好是為參數名添加一個后置下劃線而不是使用縮寫或錯誤的拼寫。因此class_ 比clss好。(也許使用同義詞來避免更好。)。
h. 方法名和實例變量
大體上和函數相同:使用小寫單詞,必要時用下劃線分隔增加可讀性.
僅為不打算作為類的公共接口的內部方法和實例變量使用一個前導下劃線.
為了避免和子類命名沖突,使用兩個前導下劃線調用Python的名稱改編規則。
Python用類名改編這些名字:如果類Foo有一個屬性名為__a,通過Foo.__a不能訪問。(執著的用戶可以通過調用Foo._Foo__a來訪問。)通常,兩個前導下劃線僅用來避免與子類的屬性名沖突。
注意:關于__names的使用存在一些爭論(見下文)。
i. 常量
常量通常在模塊級別定義,并且所有的字母都是大寫,單詞用下劃線分開。例如: MAX_OVERFLOW 和 TOTAL。
j. 繼承的設計
確定類的方法和實例變量(統稱為:“屬性”)是否公開。如果有疑問,選擇非公開;之后把其變成公開比把一個公開屬性改成非公開要容易。
公開屬性是那些你期望與你的類不相關的客戶使用的,根據你的承諾來避免向后不兼容的變更。非公開屬性是那些不打算被第三方使用的;你不保證非公開屬性不會改變甚至被刪除。
此處沒有使用術語“private”,因為Python中沒有真正私有的屬性(沒有通常的不必要的工作)。
屬性的另一個類別是“API子集”的一部分(在其它語言常被稱為“protected”)。某些類被設計為基類,要么擴展,要么修改某些方面的類的行為。在設計這樣的類的時候,要注意明確哪些屬性是公開的,哪些是API子類的一部分,哪些是真正只在你的基類中使用。
清楚這些之后,以下是 Pythonic 的指南:
公開屬性沒有前導下劃線。
如果公開屬性名和保留關鍵字沖突,給屬性名添加一個后置下劃線。這比縮寫或拼寫錯誤更可取。(然而,盡管有這樣的規定,對于任何類的變量或參數,特別是類方法的第一個參數,‘cls’是首選的拼寫方式)
注1:參見上面對類方法的參數名的建議。
對于簡單的公開數據屬性,最好只暴露屬性名,沒有復雜的訪問器/修改器方法。記住,Python為未來增強提供了一條簡單的途徑,你應該發現簡單的數據屬性需要增加功能行為。在這種情況下,使用屬性來隱藏簡單數據屬性訪問語法后面的功能實現。
注1:特性僅工作于新風格的類。
注2:盡量保持功能行為無副作用,盡管副作用如緩存通常是好的。
注3:計算開銷較大的操作避免使用特性,屬性注解使調用者相信訪問(相對)是廉價的。
如果確定你的類會被子類化,并有不想子類使用的屬性,考慮用兩個前導下劃線無后置下劃線來命名它們。這將調用Python的名稱改編算法,類名將被改編為屬性名。當子類不無意間包括相同的屬性名時,這有助于幫助避免屬性名沖突。
注1:注意改編名稱僅用于簡單類名,如果一個子類使用相同的類名和屬性名,仍然會有名字沖突。
注2:名稱改編會帶來一定的不便,如調試和getattr()。然而,名稱改編算法有良好的文檔,也容易手工執行。
注3:不是每個人都喜歡名稱改編。嘗試平衡避免意外的名稱沖突和高級調用者的可能。
公共和內部接口
任何向后兼容性保證只適用于公共接口。因此,重要的是用戶能夠清楚地區分公開和內部接口。
文檔接口被認為是公開的,除非文檔明確聲明他們是臨時或內部接口,免除通常的向后兼容保證。所有非文檔化的接口應假定為內部接口。
為了更好的支持自省,模塊應該使用all屬性顯示聲明他們公開API的名字, all設置為一個空列表表示該模塊沒有公開API。
即使all設置的適當,內部接口(包,模塊,類,函數,屬性或者其它名字)仍應以一個前導下劃線作前綴。
一個接口被認為是內部接口,如果它包含任何命名空間(包,模塊,或類)被認為是內部的。
導入名被認為是實現細節。其它模塊必須不依賴間接訪問這個導入名,除非他們是一個明確的記錄包含模塊的API的一部分,例如os.path或包的init模塊,從子模塊暴露功能。
程序設計建議
- 編寫的代碼應該不損害其他方式的Python實現(PyPy,Jython,IronPython,Cython,Psyco等等)。
例如,不要依賴CPython的高效實現字符串連接的語句形式 += b或a = a + b。這種優化即使在CPython里也是脆弱的(它只適用于某些類型),并且在不使用引用計數的實現中它完全不存在。在庫的性能易受影響的部分,應使用''.join()形式。這將確保跨越不同實現的連接發生在線性時間。
- 與單值, 比如
None
比較, 使用is
或is not
,不要用等號操作符。
同樣,如果你真正的意思是if x is not None,謹防編寫if x。例如,當測試一個默認是None的變量或參數是否被置成其它的值時。這個其它值可能是在布爾上下文為假的類型(例如容器)。
- 使用
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聲明的唯一好處(例如它可以嵌在更大的表達式里面)。
- 從
Exception
而不是BaseException
中派生出異常。直接繼承BaseException是保留那些捕捉幾乎總是錯的異常的。
設計異常層次結構基于區別,代碼可能需要捕獲異常,而不是捕獲產生異常的位置。
旨在以編程方式回答問題“出了什么問題?”,而不是只說“問題產生了”(參見PEP 3151對這節課學習內置異常層次結構的一個例子)
類的命名規則適用于此,只是當異常確實是錯誤的時候,需要在異常類名添加“Error”后綴。用于非本地的流控制或其他形式的信號的非錯誤的異常,不需要特殊的后綴。
- 適當使用異常鏈。Python3中,“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異常,這使得很難用Control-C來中斷程序,也會掩飾其它的問題。
如果想捕獲會導致程序錯誤的所有異常,使用except Exception:(空異常相當于except BaseException:)
一條好的經驗法則是限制使用空‘except’子句的兩種情況:
- 如果異常處理程序將打印或記錄跟蹤;至少用戶將會意識到有錯誤發生。
- 如果代碼需要做一些清理工作,但是隨后讓異常用raise拋出。處理這種情況用try...finally更好。
當用一個名字綁定捕獲異常時,更喜歡Python2.6中添加的明確的名稱綁定語法。
try:
process_data()
except Exception as exc:
raise DataProcessingFailedError(str(exc))
這是Python3中唯一支持的語法,并避免與舊的基于逗號的語法有關的歧義問題。
捕獲操作系統異常時,優先使用 Python 3.3引進的明確的異常層次,通過errno值自省。
另外,對于所有的try/except子句,限制try子句內只有絕對最少代碼量。這避免掩蓋錯誤。
風格良好:
try:
value = collection[key]
except KeyError:
return key_not_found(key)
else:
return handle_value(value)
風格不良:
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)
使用字符串方法代替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),并且字節對象不再是string的一種(而是一個integers序列)
對于序列,(字符串,列表,元組),利用空序列是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:
函數注釋
為了包含 PEP 484 ,風格指南的函數注釋部分進行了更新
為了向前兼容,Python 3中的函數注釋代碼應該更好地使用PEP 484語法。(在前一節中有一些注釋格式的建議)。
不再鼓勵之前在這個PEP中推薦的嘗試性的注釋風格。
然而,除了標準庫,現在鼓勵使用PEP 484中的嘗試性的規則。例如,使用PEP 484風格類型的注釋標記一個大型第三方庫或應用,檢查添加這些注釋有多容易,并觀察他們的是否會使代碼更加易懂。
Python標準庫應該對使用這些注釋采取保守的態度,但允許在新的代碼或大型重構中使用這些注釋規則。
對于代碼,如果想要用函數注釋的不同用法,推薦使用如下格式的注釋:
# type: ignore
在靠近文件的頂部,這告訴類型檢查器忽略所有注釋。(更詳細的關閉類型檢查器報錯的方法,可以在 PEP 484 中查找)。
類型檢查是可選的,單獨的工具。Python解釋器在默認情況下不應該發布任何類型檢查的消息,并且應該不改變他們基于注釋的行為。
不想使用類型檢查的用戶可以自由地忽略它們。然而,可能用戶的第三方庫包可能想要在這些包種運行類型檢查。為此,PEP 484建議使用存根文件:'.pyi' 文件被類型檢查器讀取相應的'.py'文件的偏好。存根文件可以通過資源和庫一起發布,或在獲得庫作者的許可時單獨發布。
對于需要向后兼容的代碼,函數注釋可以添加到評論格式中。可以查看 PEP 484 中相關的章節。
參考
PEP 8 -- Style Guide for Python Code
PEP8中文版 -- Python編碼風格指南(上)
PEP8中文版 -- Python編碼風格指南(中)
PEP8中文版 -- Python編碼風格指南(下)
Python開發編碼規范
Python 編碼風格指南中譯版