Python 編碼風格指南中譯版(Google SOC)
針對Python Style Guide Jun 18, 2009 版本翻譯
- 譯文發布于:http://www.elias.cn/Develop/PythonStyleGuide
- 譯者:elias DOT soong AT gmail DOT com
On this page... (hide)
-
1. 概述
- 1.1 Python 語言方面的準則
- 1.2 Python 編碼風格方面的準則
-
2. Python 語言方面的準則
- 2.1 pychecker
- 2.2 導入模塊和包
- 2.3 完整路徑導入
- 2.4 異常處理
- 2.5 全局變量
- 2.6 內嵌/本地/內部類和函數
- 2.7 List Comprehensions
- 2.8 默認迭代器和運算符
- 2.9 生成器
- 2.10 使用 apply filter map reduce
- 2.11 Lambda functions
- 2.12 默認參數值
- 2.13 Properties
- 2.14 布爾內置類型
- 2.15 String 方法
- 2.16 靜態域
- 2.17 函數和方法修飾符
- 2.18 線程
- 2.19 高級特性
- 3. Python 編碼風格方面的準則
本文是向 Melange 項目貢獻 Python 代碼的編碼風格指南。
SoC framework項目以及在此之上構建的Melange web applications都是用 Python 語言實現的(因為 Python 是Google App Engine目前支持的唯一一種編程語言)。本編碼風格指南是向 Melange 項目貢獻 Python 代碼的“要做”和“不要做”的列表。
下面這些規則不是原則或建議,而是嚴格的規定。你不能不理會這些規定除非是確實需要使用并被允許了,但仍然要注意本指南結尾處一致性部分的建議。
如果你有關于這些準則的問題,可以向開發者郵件列表發郵件詢問。請在郵件標題寫上“Python style:”,以便在郵件列表歸檔中更容易定位這些討論。
本編碼風格指南寫在 Melange 項目的 Wiki 上,可以遵循已有的文檔復查流程?進行修改。但不應該輕易修改本文,因為一致性是本指南的一個關鍵目標,而且不能因為新的風格指南變化而需要更新舊代碼。
1.? 概述
1.1? Python 語言方面的準則
- pychecker: 建議使用
- 導入模塊和包: 可以,但不要 import *
- 完整路徑導入: 可以
- 異常處理: 可以
- 全局變量: 謹慎使用
- 內嵌/本地/內部類和函數: 可以
- List Comprehensions: 可以用,如果簡明易懂的話
- 默認迭代器和運算符: 可以
- 生成器: 可以
- 使用 apply、 filter、 map、 reduce: 對one-liner來說可以
- Lambda 函數: 對one-liner來說可以
- 默認參數值: 可以
- Properties: 可以
- True/False 求值: 可以
- 布爾內置類型: 可以
- String 方法: 可以
- 靜態域: 可以
- 函數和方法修飾符: 適度使用
- 線程: 不要用(Google App Engine不支持)
- 高級特性: 不要用
1.2? Python 編碼風格方面的準則
所有文件都保持一致風格時程序要容易維護得多,以下是 Melange 項目的標準 Python 編碼風格規范:
- 分號: 避免使用
- 每行長度: 最多80列
- 圓括號: 吝嗇地用
- 縮進: 4 空格(不要用 tab ),和PEP8有所區別
- 空行: 對函數和類用 2 個空行分隔,對類的方法用 1 個空行
- 空格: 在行內吝嗇地使用
- Python 解釋器: 用Google App Engine支持的那個: #!/usr/bin/python2.5
- 注釋: __doc__ strings、塊注釋、行內注釋
- 類: 繼承 object
- 字符串: 避免重復使用 + 和 +=
- TODO style: TODO(username): 使用一種一致的風格
- import 分組、排序和整理: 一行一個,按包名字分組順序放置,按字母順序排序
- 語句: 一行一個,避免使用分號
- 訪問控制: 輕量化風格可以用 foo,否則用 GetFoo() 和 SetFoo()
- 命名: 用 foo_bar.py 而不是 foo-bar.py,和PEP8有些區別
- 程序入口: if __name__ == '__main__':
- 總結: 看看你周圍是什么
2.? Python 語言方面的準則
2.1? pychecker
是什么: pychecker 是從 Python 源代碼中找 Bug 的工具。類似 lint ,它尋找在 C 和 C++ 那樣更少動態化的語言中通常由編譯器捕捉的那些問題。因為 Python 天生就是動態化的,其中一些警告可能并不正確;但假警報應該是相當少見的。
優點: 捕捉易犯的錯誤,比如拼寫、在賦值之前就使用變量等等。
缺點: pychecker 不是完美的。為了好好利用它,我們有時候得:a) 針對它來寫代碼 b) 禁用特定警告 c) 改進它 d) 或者忽略它。
決定: 確保在代碼上執行 pychecker。
你可以設置一個名為 __pychecker__ 的模塊級別變量來適當禁用某些警告。比如:
用這種方法禁用有個好處:我們可以很容易地找到這些禁用并且重新啟用它們。
你可以用 pychecker --help 來獲得 pychecker 警告的列表。
禁用“未被使用的變量”警告可以用 _ 作為未使用變量的標識符,或者給變量名添加 unused_ 前綴。有些情況下無法改變變量名,那你可以在函數開頭調用它們一下,比如:
d, e = (d, e) # silence pychecker
return a
最理想的是, pychecker 會竭盡全力保證這樣的“未使用聲明”是真實的。
你可以在PyChecker 主頁找到更多相關信息。
2.2? 導入模塊和包
是什么: 在模塊與模塊之間共享代碼的重用機制。
優點: 最簡單同時也是最常用的共用方法。
缺點: from foo import * 或者 from foo import Bar 很惡心而且這會使尋找模塊之間的依賴關系變難,從而導致嚴重的維護問題。
決定: 用 import x 來導入包和模塊。只有在 x 是一個包(package),而 y 是一個模塊(module)的時候才用 from x import y 。這可以讓使用者無需說明完整的包前綴就能引用模塊。比如 sound.effects.echo 包可以像下面這樣導入:
...
echo.echofilter(input, output, delay=0.7, atten=4)
即使模塊是在同一個包里,也不要直接導入模塊而不給出完整的包名字。這可能導致包被導入多次以及非預期的副作用。
例外的情況:當且僅當 Bar 是 foo 中的一個頂級 singleton,并且叫做 foo_Bar 是為了描述 Bar 與 foo 之間的關系,那么才可以用 from foo import Bar as foo_Bar 。
比如,像下面這樣是可以的:
...
user_logic.getForCurrentAccount()
以下寫法更受歡迎:
...
import soc.logic.models.user
...
models.logic.user.logic.getForCurrentAccount()
2.3? 完整路徑導入
是什么: 每個模塊都通過模塊的完整路徑位置導入和引用。
優點: 避免模塊名字之間的沖突,而且查找模塊更容易。
缺點: 部署代碼會麻煩一些,因為你必須重現整個包的分層結構。
決定: 所有新代碼都應該使用他們的包名字來引用模塊。不要為了從其他目錄導入模塊而修改 sys.path 和 PATHONPATH。(可能有些情況下仍然需要修改路徑,但只要可能的話就應該避免這樣做。)
應該像下面這樣導入:
import soc.logging
# 僅使用模塊名字引用:
from soc import logging
有些包名字可能和常用的本地變量名是一樣的。在這種情況下為了避免混亂,先導入包然后清晰地單獨導入模塊有時候也是有意義的。結果看起來像這樣:
import soc.models.user
...
user = models.user.User()
在創建時就避免易引發混亂的模塊命名可能是最佳方案,但有時選擇直觀的模塊名字(比如上面例子中 soc.models 中的那些模塊)會導致需要像上面這樣做 workaround 。
2.4? 異常處理
是什么: 異常處理指的是為處理錯誤及其他異常狀況而打破代碼塊的正常控制流。
優點: 正常操作代碼的控制流不會被錯誤處理代碼弄亂。也可以在特定情況發生時讓控制流跳過多個步驟,比如一步就從 N 層嵌套函數(nested functions)中返回,而不必繼續把錯誤代碼一步步執行到底。
缺點: 可能使控制流變得難以理解,以及在做函數庫調用時容易忽略一些錯誤情況。
決定: 異常處理是很 Pythonic 的,因此我們當然同意用它,但只是在符合以下特定條件時:
- 要像這樣拋出異常:raise MyException("Error message") 或者 raise MyException,而不要用雙參數的形式(raise MyException, "Error message") 或者 已廢棄的基于字符串的異常(raise "Error message")。
-
模塊和包應該定義自己的特定領域的基礎異常類,而且這個類應該繼承自內置的 Exception 類。這種用于一個模塊的基礎異常應該命名為 Error。
class Error(Exception):
"""Base exception for all exceptions raised in module Foo."""
pass - 永遠不要直接捕獲所有異常,也即 except: 語句,或者捕獲 Exception 和 StandardError,除非你會把異常重新拋出或者是在你線程最外層的代碼塊中(而且你會打印一個出錯信息)。在這種意義上來講, Python 是很健壯的,因為 except: 會真正捕獲包括 Python 語法錯誤在內的所有東西。使用 except: 很容易會掩蓋真正的 Bug 。
- 在 try/except 塊中使代碼量最小化。try 里頭的內容越多,就更有可能出現你原先并未預期會拋出異常的代碼拋出異常的情況。在這種情況下,try/except 代碼塊就隱藏了真正的錯誤。
- 使用 finally 子句執行一段代碼而無論 try 語句塊是否會拋出異常。這在做清除工作的時候經常是很有用的,比如關閉文件。
2.5? 全局變量
是什么: 在模塊級別聲明的變量。
優點: 有時很有用。
缺點: 有可能在導入時改變模塊的行為,因為在模塊被導入時完成了模塊級別變量的賦值。
決定: 推薦使用類變量而避免使用全局變量。以下是一些例外情況:
- 作為腳本中的默認選項。
- 模塊級別常量,例如 PI = 3.14159 。常量名應該全部使用大寫字母并用下劃線分隔,參考下文命名章節。
- 有時全局變量對緩存函數返回值或其他需要的運算結果會是挺有用的。
- 在需要的時候,全局變量應該被用作模塊的內部變量,并通過公開的模塊級函數來訪問。參考下文命名?章節。
2.6? 內嵌/本地/內部類和函數
是什么: 類可以定義在函數或另一個類的內部。函數可以定義在另一個函數的內部。內嵌函數可以只讀訪問定義在外部作用域中的變量。
優點: 允許定義只用于一個非常有限的作用域內部的工具類和工具函數,實在太方便了(ADT-y)。
缺點: 內嵌的本地類實例無法被序列化導出(pickle)。
決定: 挺好的,用吧。
2.7? List Comprehensions
是什么: List Comprehension 和 Generator Expression 提供了一種簡明高效的方法來創建列表和迭代器而無需借助 map() 、 filter() 、或者 lambda 。
優點: 簡單的 List Comprehensions 比其他列表創建技術更清晰、更簡單。Generator Expressions 非常高效,因為它避免了一下子創建整個列表。
缺點: 復雜的 List Comprehensions 或 Generator Expressions 很難讀懂。
決定: 對一行風格(one-liner)來說沒問題,或者在 x for y in z 能寫在一行里而條件語句可以寫成另一行的時候(注意下面 x 很長的、寫成三行的那個例子是可以接受的)也可以用。當問題變得更復雜時應該用循環來代替。這是一種主觀判斷。
No:
result = [(x, y) for x in range(10)
for y in range(5)
if x * y > 10]
Yes:
result = []
for x in range(10):
for y in range(5):
if x * y > 10:
result.append((x, y))
# 簡單舒服的一行風格:
squares = [x * x for x in range(10)]
# 寫成兩行的 Generator (不是 List Comprehension)例子,也是可以的:
Eat(jelly_bean for jelly_bean in jelly_beans
if jelly_bean.color == 'black')
# 寫成三行的這個例子是否仍然比等價的循環具有更好的可讀性還存在爭議:
result = [someReallyLongWayToEatAJellyBean(jelly_bean)
for jelly_bean in jelly_beans
if jelly_bean != 'black']
2.8? 默認迭代器和運算符
是什么: 容器類型(比如字典和列表)定義了默認迭代器和從屬關系測試運算符(例如 in 和 not in)。
優點: 默認迭代器和運算符又簡單又高效,它們無需額外方法調用就能直接表達操作。使用默認運算符來編寫函數是一種通用性較強的做法,因為它可以用于任何支持這些操作的數據類型。
缺點: 你無法通過閱讀方法名字就說出對象的類型(比如說 has_key() 表示一個字典)。這同時也是一個優點。
決定: 在支持它們的類型上使用默認迭代器和運算符,比如列表、字典、和文件。當然這些內置類型同時也定義了迭代器方法。對返回列表的方法傾向于使用迭代器方法,但你不應該在迭代的過程中修改容器中的內容。
Yes:
if key not in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in dict.iteritems(): ...
No:
if not adict.has_key(key): ...
for line in afile.readlines(): ...
2.9? 生成器
是什么: 生成器函數返回一個迭代器,而這個迭代器每次執行 yield 語句生成一個計算結果。每次生成一個計算結果之后,生成器函數的運行時狀態被掛起,直到需要生成下一個計算結果時。
優點: 代碼更簡單。因為對每次調用來說,本地變量和控制流的狀態都是被保留的。生成器比用計算結果一次性填滿整個列表的函數更節省內存。
缺點: 沒有。
決定: 很好。在生成器函數的 __doc__ String 中使用 "Yields:" 而不是 "Returns:" 。
2.10? 使用 apply filter map reduce
是什么: 實用的內置列表操作函數。通常和 lambda 函數一起用。
優點: 代碼很緊湊。
缺點: 高階函數式編程恐怕更難理解。
決定: 對簡單代碼和喜歡把代碼寫成一行的人來說,盡可能用 List Comprehension 而限制使用這些內置函數。一般來說,如果代碼在任何地方長度超過60到80個字符,或者使用了多層函數調用(例如,map(lambda x: x[1], filter(...)))),那這就是一個信號提醒你最好把它重新寫成一個普通的循環。比較:
map/filter:
list comprehensions:
2.11? Lambda functions
是什么: Lambda 定義僅包含表達式、沒有其他語句的匿名函數,通常用于定義回調或者用于 map() 和 filter() 這樣高階函數的運算符。
優點: 方便。
缺點: 比本地函數更難閱讀和調試。沒有名字意味著堆棧追蹤信息更難理解。而且函數只能包含一個表達式,因而表達能力也比較有限。
決定: 對喜歡把代碼寫成一行的人來說沒什么問題。只要 lambda 函數中的代碼長度超過60到80個字符,那可能把它定義成一個標準(或者嵌套的)函數更合適。
對加法這樣的普通操作,應該使用 operator 模塊中的函數而不是用 lambda 函數。例如,應該用 operator.add 而不是 lambda x, y: x + y。(內置的 sum() 函數其實比這兩者中的任何一個都更合適。)
2.12? 默認參數值
是什么: 你可以給函數的參數列表中最靠后的幾個變量指定取值,比如 def foo(a, b=0): 。如果只用一個參數來調用 foo ,b 將被設置為 0 。如果用兩個參數來調用它, b 將使用第二個參數的值。
優點: 通常你會大量使用函數的默認值,但偶爾會需要覆蓋這些值。默認參數值允許用一種簡單的辦法來做到這一點,而不必為偶爾的例外情形定義一大堆函數。而且, Python 不支持方法/函數重載,而參數默認值是“模仿”重載行為的一種簡單方法。
缺點: 默認參數值會在模塊加載的時候被賦值。如果參數是一個列表或者字典之類的可變對象就有可能造成問題。如果函數修改了這個對象(比如在列表最后附加了一個新的項),那默認值也就改變了。
決定: 在以下限制條款下是可以使用的:
- 不要把可變對象當作函數或方法定義的默認值。
Yes:
if b is None:
b = []
No:
...
調用函數的代碼必須對默認參數使用指名賦值(named value)。這多少可以幫助代碼的文檔化,并且當增加新參數進來時幫助避免和發現破壞原有接口。
...
Yes:
foo(1, b=2)
No:
2.13? Properties
是什么: 在運算量很小時,把對屬性的 get 和 set 方法調用封裝為標準的屬性訪問方式的一個方法。
優點: 對簡單的屬性訪問來說,去掉直率的 get 和 set 方法調用提高了代碼的可讀性。允許延后計算。考慮到 Pythonic 的方法來維護類的接口。在性能方面,當直接變量訪問是合理的,允許 Properties 就省略了瑣碎的屬性訪問方法,而且將來仍然可以在不破壞接口的情況下重新加入屬性訪問方法。
缺點: Properties 在 getter 和 setter 方法聲明之后生效,也就是要求使用者注意這些方法在代碼很靠后的地方才能被使用(除了用 @property 描述符創建的只讀屬性之外 —— 見下文詳述)。必須繼承自 object 。會像運算符重載一樣隱藏副作用。對子類來說會很難理解。
決定: 在那些你通常本來會用簡單、輕量的訪問/設置方法的代碼中使用 Properties 來訪問和設置數據。只讀的屬性應該用 @property 描述符?來創建。
如果 Property 自身沒有被覆蓋,那 Properties 的繼承并非顯而易見。因此使用者必須確保訪問方法被間接調用,以便確保子類中被覆蓋了的方法會被 Property 調用(使用“模板方法(Template Method)”設計模式)。
Yes:
class Square(object):
"""基類 square 有可寫的 'area' 屬性和只讀的 'perimeter' 屬性。
可以這樣使用:
>>> sq = Square(3)
>>> sq.area
9
>>> sq.perimeter
12
>>> sq.area = 16
>>> sq.side
4
>>> sq.perimeter
16
"""
def __init__(self, side):
self.side = side
def _getArea(self):
"""計算 'area' 屬性的值"""
return self.side ** 2
def __getArea(self):
"""對 'area' 屬性的間接訪問器"""
return self._getArea()
def _setArea(self, area):
"""對 'area' 屬性的設置器"""
self.side = math.sqrt(area)
def __setArea(self, area):
"""對 'area' 屬性的間接設置器"""
self._setArea(area)
area = property(__getArea, __setArea,
doc="""Get or set the area of the square""")
@property
def perimeter(self):
return self.side * 4
是什么: 在布爾型上下文下, Python 把一些特定的取值當作“false”處理。快速的“經驗法則”是所有的“空”值都會被認為是“false”,也即 0、None、[]、{}、"" 在布爾型上下文中都會被當作“false”。
優點: 使用 Python 的布爾型條件更容易閱讀而且更不容易產生錯誤。在大多數情況下,這也是運行速度更快的選擇。
缺點: 對 C/C++ 開發者來說可能會看起來很奇怪。
決定: 在所有可能的情況下使用這種“隱含”的 false ,比如用 if foo: 而不是 if foo != []: 。這有幾條你應該時刻注意的限制條件:
- 與具有唯一性的值比如 None 進行比較時總是應該使用 is 或者 is not。而且,留神在寫 if x: 而你的實際意思是 if x is not None: 的時候,比如,測試一個默認值是 None 的變量或參數是否被設置為其他值。這里的“其他值”就有可能是在布爾型上下文中被認為是 false 的值!
- 對序列(strings、 lists、 tuples)來說,可以利用空序列就是 false 這一事實,也就是說 if not seq: 或者 if seq: 比 if len(seq): 或者 if not len(seq): 這種形式要更好。
- 注意 '0' (也即 0 當作字符串)會被求值為 true。
2.14? 布爾內置類型
是什么: 從 Python 2.3 開始加入了布爾類型,也即加入了兩個新的內置常量:True 和 False。
優點: 這使代碼更容易閱讀而且與之前版本中使用整數作為布爾型的做法向后兼容。
缺點: 沒有。
決定: 使用布爾型。
2.15? String 方法
是什么: String 對象包括一些以前是 string 模塊中的函數的方法。
優點: 無需導入 string 模塊,而且這些方法在標準 byte 字符串和 unicode 字符串上都能使用。
缺點: 沒有。
決定: 用吧。 string 模塊已經被廢棄了,現在更推薦使用 String 方法。
No: words = string.split(foo, ':')
Yes: words = foo.split(':')
2.16? 靜態域
是什么: 被嵌套的 Python 函數(nested Python function)可以引用定義在容器函數(enclosing function)中的變量,但無法對它們重新賦值。變量綁定依據靜態域(Lexical Scoping)決定,也就是說,基于靜態的程序文本(static program text)。代碼塊中對一個名字的任意賦值都將導致 Python 把對這個名字的所有引用當作本地變量,即使是先調用后賦值。如果存在全局變量聲明,那這個名字就會被當作是全局變量。
以下是使用這一特性的例子:
"""getAdder 返回把數字與一個給定數字相加的函數。"""
def anAdder(summand2):
return summand1 + summand2
return anAdder
優點: 一般會得到更清晰、更優雅的代碼。而且特別符合有經驗的 Lisp 和 Scheme (以及 Haskell、ML 等等)程序員的習慣。
缺點: 沒有。
決定: 可以用。
2.17? 函數和方法修飾符
是什么: 在 Python 2.4 版本增加了函數和方法的修飾符(又稱“@ notation”)。最常用的修飾符是 @classmethod 和 @staticmethod,用于把普通的方法轉化為類方法或靜態方法。然而,修飾符語法同樣允許用戶自定義修飾符。具體地,對函數 myDecorator 來說:
@myDecorator
def aMethod(self):
# method body ...
和如下代碼是等價的:
def aMethod(self):
# method body ...
aMethod = myDecorator(aMethod)
優點: 能夠優雅地對指定方法進行變形,而且這種變形避免了重復代碼,強化了通用性(enforce invariants)等。
缺點: 修飾符能對函數的參數和返回值進行任意操作,會導致出乎意料之外的隱含行為。除此之外,修飾符會在導入階段被執行。修飾符代碼中的故障幾乎是不可能被修復的。
決定: 在有明顯好處時使用修飾符是明智的。修飾符應該和函數遵循同樣的導入和命名準則。修飾符的 __doc__ string 應該清晰地聲明該函數是一個修飾符,而且要為修飾符寫單元測試。
避免在修飾符資深引入外部依賴關系(比如,不要依賴文件、 sockets 、數據庫連接等),因為在修飾符運行時(在導入階段,也許源于 pychecker 或其他工具)這些都有可能是無效的。在所有情況下都應(盡可能)保證使用有效參數調用修飾符時能成功運行。
修飾符是“頂級代碼”(top level code)的一種特例 —— 參考主入口章節的更多討論。
2.18? 線程
Google App Engine 不支持線程,所以在 SoC framework 和 Melange Web 應用程序中也別用它。
2.19? 高級特性
是什么: Python 是一種極為靈活的語言,提供諸如 metaclass、 bytecode 訪問、即時編譯、動態繼承、 object reparenting、 import hacks、反射、修改系統內部結構等很多很炫的特性。
優點: 這些都是很強大的語言特性,能使你的代碼更緊湊。
缺點: 在并非絕對必要的時候使用這些很“酷”的特性是很誘人的。里面使用了這些并不常見的特性的代碼會更難讀、更難懂、更難 debug。也許在剛開始的時候好像還沒這么糟(對代碼的原作者來說),但當你重新回到這些代碼,就會覺得這比長點但簡單直接的代碼要更難搞。
決定: 在 Melange 代碼中避免使用這些特性。例外的情形在開發者郵件列表中討論。
3.? Python 編碼風格方面的準則
3.1? 分號
不要用分號作為你的行結束符,也不要利用分號在同一行放兩個指令。
3.2? 每行長度
一行最多可以有80個字符。
例外: 導入模塊的行可以超過80個字符再結束。
確保 Python 隱含的連接行(line joining)放在圓括號、方括號或大括號之間。如果需要的話,你可以在表達式兩頭放一堆額外的圓括號。
Yes:
emphasis=None, highlight=0)
if ((width == 0) and (height == 0)
and (color == 'red') and (emphasis == 'strong')):
當表示文本的字符串(literal string)一行放不下的時候,用圓括號把隱含的連接行括起來。
'long long long long long long string')
注意上例中連續行中元素的縮進,參考縮進章節的解釋。
3.3? 圓括號
吝嗇地使用圓括號。在以下情況下別用:
- 在 return 語句中。
- 在條件判斷語句中。除非是用圓括號來暗示兩行是連在一起的(參見上一節)。
- 在元組(tuple)周圍。除非因為語法而不得不加或是為了顯得更清晰。
但在下列情況下是可以用圓括號的:
- 暗示兩行是連在一起的。
- 用來括起長表達式中的子表達式(包括子表達式是條件判斷語句一部分的情形)。
實際上,在子表達式周圍用圓括號比單純依賴運算符優先級要好。
Yes:
while x:
if x and y:
if not x:
if (x < 3) and (not y):
return foo
for x, y in dict.items():
x, (y, z) = funcThatReturnsNestedTuples()
No:
while (x):
if not(x):
if ((x < 3) and (not y)):
return (foo)
for (x, y) in dict.items():
(x, (y, z)) = funcThatReturnsNestedTuples()
3.4? 縮進
注意和PEP8不同,這里遵循作為本文起源的原始 Google Python 編碼風格指南。
用兩空格縮進代碼塊。不要用 tab 或者混用 tab 和空格。如果要暗示兩行相連,要么就把被包裝的元素縱向對其,就像“每行長度”章節中的例子那樣;要么就用4個空格(不是兩個,這樣可以和后面緊跟著的嵌套代碼塊區分開,避免混淆)作懸掛縮進(hanging indent),在這種情況下,首行不應該放任何參數。
Yes:
foo = longFunctionName(var_one, var_two,
var_three, var_four)
# 4空格懸掛縮進,而且首行空著:
foo = longFunctionName(
var_one, var_two, var_three,
var_four)
No:
foo = longFunctionName(var_one, var_two,
var_three, var_four)
# 不應該用兩空格的懸掛縮進:
foo = longFunctionName(
var_one, var_two, var_three,
var_four)
3.5? 空行
在頂級定義(可以是函數或者類定義)之間加兩個空行。在方法定義之間以及“ class ”那一行與第一個方法之間加一個空行。在__doc__ string和它后面的代碼之間加一個空行。在函數和方法內部你認為合適的地方加一個空行。
在文件最后總是加一個空行,這可以避免很多 diff 工具生成“No newline at end of file”信息。
3.6? 空格
在圓括號、方括號、大括號里面不要加空格。
Yes: spam(ham[1], {eggs: 2}, [])
No: spam( ham[ 1 ], { eggs: 2 }, [ ] )
在逗號、分號、冒號前面不要加空格。逗號、分號、冒號后面必須加空格,除非那是行尾。
Yes:
print x, y
x, y = y, x
No:
print x , y
x , y = y , x
在表示參數、列表、下標、分塊開始的圓括號/方括號前面不要加空格。
Yes: spam(1)
No: spam (1)
Yes: dict['key'] = list[index]
No: dict ['key'] = list [index]
在二元運算符兩邊各家一個空格,包括:賦值(=)、比較(==、<、>、!=、<>、<=、>=、in、not in、is、is not)、以及布爾運算符(and、or、not)。你肯定能判斷出是否應該在算術運算符周圍加空格,因為在二元運算符兩邊加空格的原則總是一致的。
Yes: x == 1
No: x<1
等號(“=”)用于指名參數或默認參數值時,兩邊不要加空格。
Yes: def Complex(real, imag=0.0): return Magic(r=real, i=imag)
No: def Complex(real, imag = 0.0): return Magic(r = real, i = imag)
3.7? Python 解釋器
模塊開頭應該是一個“shebang”行,用于指定執行此程序的 Python 解釋器:
Google App Engine 要求使用 Python 2.5。
3.8? 注釋
Doc strings
Python 有一種獨特的注釋風格稱為 __doc__ String。包、模塊、類、函數的第一個語句如果是字符串那么就是一個 __doc__ String。這種字符串可以用對象的 __doc__() 成員函數自動提取,而且會被用于 pydoc。(試著在你的模塊上運行 pydoc 來看看它是怎么工作的。)我們對 __doc__ String 的約定是:用三個雙引號把字符串括起來。 __doc__ String 應該這樣組織:首先是一個以句號結束的摘要行(實實在在的一行,不多于80個字符),然后接一個空行,再接 __doc__ String 中的其他內容,并且這些文字的起始位置要和首行的第一個雙引號在同一列。下面幾節是關于 __doc__ String 格式更進一步的原則
模塊
每個文件開頭都應該包含一個帶有版權信息和許可聲明的塊注釋。
版權和許可聲明
#
# Copyright [current year] the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
然后接著寫描述模塊內容的__doc__ String,其中作者信息應該緊跟著許可聲明。如果還給出了作者郵件地址,那添加到 __authors__ 列表的整個作者字符串應該就是一個與 RFC 2821 兼容的電子郵件地址。
模塊頭及作者信息
留一個空行。本 __doc__ string 的其他部分應該包括模塊或腳本的全面描述。作為可選項,還可以包括導出的類和函數的簡要描述。
ClassFoo: 一行概述。
functionBar(): 一行概述。
"""
__authors__ = [
# 請按照姓氏字母順序排列:
'"John Smith" <johnsmith@example.com>',
'"Joe Paranoid" <joeisgone@example.com>', # 應提供電子郵件地址
]
如果新的貢獻者還沒添加到AUTHORS file中,那應該在該貢獻者第一次向版本控制工具提交代碼時同時把他/她添加到這個文件里。
函數和方法
如果不是用途非常明顯而且非常短的話,所有函數和方法都應該有 __doc__ string 。此外,所有外部能訪問的函數和方法,無論有多短、有多簡單,都應該有 __doc__ string 。 __doc__ string 應該包括函數能做什么、輸入數據的具體描述(“Args:”)、輸出數據的具體描述(“Returns:”、“Raises:”、或者“Yields:”)。 __doc__ string 應該能提供調用此函數相關的足夠信息,而無需讓使用者看函數的實現代碼。如果參數要求特定的數據類型或者設置了參數默認值,那 __doc__ string 應該明確說明這兩點。“Raises:”部分應該列出此函數可能拋出的所有異常。生成器函數的 __doc__ string 應該用“Yields:”而不是Returns:。
函數和方法的 __doc__ string 一般不應該描述實現細節,除非其中涉及非常復雜的算法。在難以理解的代碼中使用塊注釋或行內注釋會是更合適的做法。
"""取出表中的多行內容。
Args:
table: 打開的表。 Table 類的實例。
keys: 字符串序列,表示要從表中取出的行的鍵值。
Returns:
一個字典,映射指定鍵值與取出的表中對應行的數據:
{'Serak': ('Rigel VII', 'Preparer'),
'Zim': ('Irk', 'Invader'),
'Lrrr': ('Omicron Persei 8', 'Emperor')}
如果 keys 參數中的鍵值沒有出現在字典里,就表示對應行在表中沒找到。
Raises:
IOError: 訪問 table.Table 對象時發生的錯誤。
"""
pass
類
類應該在描述它的類定義下面放 __doc__ string 。如果你的類有公開屬性值,那應該在 __doc__ string 的 Attributes: 部分寫清楚。
"""這里是類的概述。
詳細的描述信息……
詳細的描述信息……
Attributes:
likes_spam: 布爾型,表示我們是否喜歡垃圾郵件。
eggs: 整數,數我們下了多少蛋。
"""
def __init__(self, likes_spam=False):
"""拿點什么來初始化 SampleClass 。
Args:
likes_spam: 初始化指標,表示 SampleClass 實例是否喜歡垃圾郵件(默認是 False)。
"""
self.likes_spam = likes_spam
self.eggs = 0
def publicMethod(self):
"""執行一些操作。"""
pass
塊注釋及行內注釋
加注釋的最后一個位置是在難以理解的代碼里面。如果你打算在下一次代碼復查(code review)的時候解釋這是什么意思,那你應該現在就把它寫成注釋。在開始進行操作之前,就應該給復雜的操作寫幾行注釋。對不直觀的代碼則應該在行末寫注釋。
# 數組的長度來推斷可能的位置,然后做二分法檢索找到準確的值。
if i & (i-1) == 0: # 當且僅當 i 是 2 的冪時,值為 true
這些注釋應該和代碼分開才更易讀。在塊注釋值錢應該加一個空行。一般行末注釋應該和代碼之間至少空兩個格。如果連續幾行都有行末注釋(或者是在一個函數中),可以把它們縱向對齊,但這不是必須的。
另一方面,不要重復描述代碼做的事。假設正在讀代碼的人比你更懂 Python (盡管這不是你努力的方向)。On the other hand, never describe the code. Assume the person readingthe code knows Python (though not what you're trying to do) betterthan you do.
3.9? 類
如果不從其他基類繼承,那就應該明確地從 object 基類繼承。這一條對嵌套類也適用。
No:
pass
class OuterClass:
class InnerClass:
pass
Yes:
pass
class OuterClass(object):
class InnerClass(object):
pass
class ChildClass(ParentClass):
"""已經從另一個類顯式繼承了。"""
pass
從 object 繼承是為了讓類屬性能夠正常工作,這會避免我們一旦切換到 Python 3000 時,打破已經習慣了的特有風格。同時這也定義了一些特殊函數,來實現對象(object)的默認語義,包括:__new__、__init__、__delattr__、__getattribute__、__setattr__、__hash__、__repr__、和 __str__。
3.10? 字符串
應該用 % 運算符來格式化字符串,即使所有的參數都是字符串。不過你也可以在 + 和 % 之間做出你自己最明智的判斷。
No:
x = imperative + ', ' + expletive + '!'
x = 'name: ' + name + '; score: ' + str(n)
Yes:
x = '%s, %s!' % (imperative, expletive)
x = 'name: %s; score: %d' % (name, n)
應該避免在循環中用 + 或 += 來連續拼接字符串。因為字符串是不變型,這會毫無必要地建立很多臨時對象,從而二次方級別的運算量而不是線性運算時間。相反,應該把每個子串放到 list 里面,然后在循環結束的時候用 ''.join() 拼接這個列表。
No:
for last_name, first_name in employee_list:
employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
employee_table += '</table>'
Yes:
for last_name, first_name in employee_list:
items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
items.append('</table>')
employee_table = ''.join(items)
對多行字符串使用 """ 而不是 '''。但是注意,通常使用隱含的行連接(implicit line joining)會更清晰,因為多行字符串不符合程序其他部分的縮進風格。
No:
不要這么用。
"""
Yes:
"所以應該這樣用。\n")
3.11? TODO style
在代碼中使用 TODO 注釋是臨時的、短期的解決方案,或者說是足夠好但不夠完美的辦法。
TODO 應該包括全部大寫的字符串 TODO ,緊接用圓括號括起來的你的用戶名: TODO(username) 。其中冒號是可選的。主要目的是希望有一種一致的 TODO 格式,而且可以通過用戶名檢索。
# TODO(anotheruser) 用 relations 來修改這兒。
如果你的 TODO 是“在未來某個時間做某事”的形式,確保你要么包括非常確定的日期(“Fix by November 2008”)或者非常特殊的事件(“在數據庫中的 Foo 實體都加上新的 fubar 屬性之后刪除這段代碼。”)。
3.12? import 分組及順序
應該在不同的行中做 import :
Yes:
import os
No:
import 總是放在文件開頭的,也即在所有模塊注釋和 __doc__ string 的后面,在模塊全局變量及常量的前面。import 應該按照從最常用到最不常用的順序分組放置:
- import 標準庫
- import 第三方庫
- import Google App Engine 相關庫
- import Django 框架相關庫
- import SoC framework 相關庫
- import 基于 SoC 框架的模塊
- import 應用程序特有的內容
應該按照字母順序排序,但所有以 from ... 開頭的行都應該靠前,然后是一個空行,再然后是所有以 import ... 開頭的行。以 import ... 開頭的標準庫和第三方庫的 import 應該放在最前面,而且和其他分組隔開:
import b_standard
import a_third_party
import b_third_party
from a_soc import f
from a_soc import g
import a_soc
import b_soc
在 import/from 行中,語句應該按照字母順序排序:
from a import g
from a.b import h
from a.d import e
import a.b
import a.b.c
import a.d.e
3.13? 語句
一般一行只放一個語句。但你可以把測試和測試結果放在一行里面,只要這樣做合適。具體來說,你不能這樣寫 try/except ,因為 try 和 except 不適合這樣,你只可以對不帶 else 的 if 這么干。
Yes:
No:
else: fuBaz(foo)
try: fuBar(foo)
except ValueError: fuBaz(foo)
try:
fuBar(foo)
except ValueError: fuBaz(foo)
3.14? 訪問控制
如果存取器函數很簡單,那你應該用公開的變量來代替它,以避免 Python 中函數調用的額外消耗。在更多功能被加進來時,你可以用 Property 特性來保持語法一致性。
另一方面,如果訪問更復雜,或者訪問變量的成本較為顯著,那你應該用函數調用(遵循命名準則),比如 getFoo() 或者 setFoo()。如果過去的行為允許通過 Property 訪問,那就別把新的存取器函數綁定到 Property 上。所有仍然企圖用舊方法訪問變量的代碼應該是非常顯眼的,這樣調用者會被提醒這種改變是比較復雜的。
3.15? 命名
要避免的命名方式
- 使用單個字符命名,除非是計數器或迭代器。
- 在任何包或模塊的名字里面使用減號。
- __double_leading_and_trailing_underscore__ 在變量開頭和結尾都使用兩個下劃線(在 Python 內部有特殊含義)。
命名約定
注意這里某些命名約定和PEP8不一樣,而是遵循“Google Python 編碼風格指南”的原始版本,也即本編碼風格指南的起源。
- “Internal”表示模塊內部或類中的保護域(protected)和私有域。
- 變量名開頭加一個下劃線(_)能對保護模塊中的變量及函數提供一些支持(不會被 import * from 導入)。
- 在實例的變量和方法開頭加兩個下劃線(__)能有效地幫助把該變量或方法變成類的私有內容(using name mangling)。
- 把模塊中相關的類和頂級函數放在一起。不像 Java ,這里無需要求自己在每個模塊中只放一個類。但要確保放在同一模塊中的類和頂級函數是高內聚的。
- 對類名使用駝峰式(形如 CapWords),而對模塊名使用下劃線分隔的小寫字母(lower_with_under.py)。
命名樣例
類別 | 公開的 | 內部的 |
Packages | lower_with_under | |
Modules | lower_with_under | _lower_with_under |
Classes | CapWords | _CapWords |
Exceptions | CapWords | |
Functions | firstLowerCapWords() | _firstLowerCapWords() |
Global/Class Constants | CAPS_WITH_UNDER | _CAPS_WITH_UNDER |
Global/Class Variables | lower_with_under | _lower_with_under |
Instance Variables | lower_with_under | _lower_with_under (protected) or __lower_with_under (private) |
Method Names * | firstLowerCapWords() | _firstLowerCapWords() (protected) or __firstLowerCapWords() (private) |
Function/Method Parameters | lower_with_under | |
Local Variables | lower_with_under |
* 考慮只在首選項設置中使用對公開屬性的直接訪問來作為 getters 和 setters,因為函數調用在 Python 中是比較昂貴的,而 property 之后可以用來把屬性訪問轉化為函數調用而無需改變訪問語法。
3.16? 程序入口
所有的 Python 源代碼文件都應該是可導入的。在 Python 中,pychecker、 pydoc、 以及單元測試都要求模塊是可導入的。你的代碼應該總是在執行你的主程序之前檢查if __name__ == '__main__': ,這樣就不會在模塊被導入時執行主程序。大寫 main() 是故意要和命名約定的其他部分不一致,也就是說建議寫成 Main() 。
# 參數解析
main()
在模塊被導入時,所有頂級縮進代碼會被執行。注意不要調用函數、創建對象、或者做其他在文件被 pycheck 或 pydoc 的時候不應該做的操作。
3.17? 總結
要一致
如果你在編寫代碼,花幾分鐘看看你周圍的代碼并且弄清它的風格。如果在 if 語句周圍使用了空格,那你也應該這樣做。如果注釋是用星號組成的小盒子圍起來的,那你同樣也應該這樣寫。
編碼風格原則的關鍵在于有一個編程的通用詞匯表,這樣人們可以人民可以集中到你要說什么而不是你要怎么說。我們在這 里提供全局的編碼風格規則以便人們知道這些詞匯,但局部風格也重要。如果你加入文件中的代碼看起來和里面已經有的代碼截然不同,那在讀者讀它的時候就會被 破壞節奏。盡量避免這樣。
from:
ref:http://www.elias.cn/Python/PythonStyleGuide?from=Develop.PythonStyleGuide