Python 編碼風格指南中譯版(Google SOC)

轉載自:http://www.open-open.com/lib/view/open1420789115343.html

針對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 編碼風格方面的準則

3.1 分號

3.2 每行長度

3.3 圓括號

3.4 縮進

3.5 空行

3.6 空格

3.7 Python 解釋器

3.8 註釋

3.9 類

3.10 字符串

3.11 TODO style

3.12 import 分組及順序

3.13 語句

3.14 訪問控制

3.15 命名

3.16 程序入口

3.17 總結

本文是向 Melange 項目貢獻 Python 代碼的編碼風格指南。


SoC framework項目以及在此之上構建的Melange web applications都是用 Python 語言實現的(因爲 Python 是Google App Engine目前支持的唯一一種編程語言)。本編碼風格指南是向 Melange 項目貢獻 Python 代碼的“要做”和“不要做”的列表。


下面這些規則不是原則或建議,而是嚴格的規定。你不能不理會這些規定除非是確實需要使用並被允許了,但仍然要注意本指南結尾處一致性部分的建議。


如果你有關於這些準則的問題,可以向開發者郵件列表發郵件詢問。請在郵件標題寫上“Python style:”,以便在郵件列表歸檔中更容易定位這些討論。


本編碼風格指南寫在 Melange 項目的 Wiki 上,可以遵循已有的文檔複查流程?進行修改。但不應該輕易修改本文,因爲一致性是本指南的一個關鍵目標,而且不能因爲新的風格指南變化而需要更新舊代碼。


(Edit Section ↓)

1. 概述

(Edit Section ↓)

1.1 Python 語言方面的準則

pychecker: 建議使用

導入模塊和包: 可以,但不要 import *

完整路徑導入: 可以

異常處理: 可以

全局變量: 謹慎使用

內嵌/本地/內部類和函數: 可以

List Comprehensions: 可以用,如果簡明易懂的話

默認迭代器和運算符: 可以

生成器: 可以

使用 apply、 filter、 map、 reduce: 對one-liner來說可以

Lambda 函數: 對one-liner來說可以

默認參數值: 可以

Properties: 可以

True/False 求值: 可以

布爾內置類型: 可以

String 方法: 可以

靜態域: 可以

函數和方法修飾符: 適度使用

線程: 不要用(Google App Engine不支持)

高級特性: 不要用

(Edit Section ↓)

1.2 Python 編碼風格方面的準則

所有文件都保持一致風格時程序要容易維護得多,以下是 Melange 項目的標準 Python 編碼風格規範:


分號: 避免使用

每行長度: 最多80列

圓括號: 吝嗇地用

縮進: 4 空格(不要用 tab ),和PEP8有所區別

空行: 對函數和類用 2 個空行分隔,對類的方法用 1 個空行

空格: 在行內吝嗇地使用

Python 解釋器: 用Google App Engine支持的那個: #!/usr/bin/python2.5

註釋: __doc__ strings、塊註釋、行內註釋

__doc__ Strings

模塊

函數和方法

塊註釋及行內註釋

類: 繼承 object

字符串: 避免重複使用 + 和 +=

TODO style: TODO(username): 使用一種一致的風格

import 分組、排序和整理: 一行一個,按包名字分組順序放置,按字母順序排序

語句: 一行一個,避免使用分號

訪問控制: 輕量化風格可以用 foo,否則用 GetFoo() 和 SetFoo()

命名: 用 foo_bar.py 而不是 foo-bar.py,和PEP8有些區別

程序入口: if __name__ == '__main__':

總結: 看看你周圍是什麼

(Edit Section ↓)

2. Python 語言方面的準則

(Edit Section ↓)

2.1 pychecker

是什麼: pychecker 是從 Python 源代碼中找 Bug 的工具。類似 lint ,它尋找在 C 和 C++ 那樣更少動態化的語言中通常由編譯器捕捉的那些問題。因爲 Python 天生就是動態化的,其中一些警告可能並不正確;但假警報應該是相當少見的。


優點: 捕捉易犯的錯誤,比如拼寫、在賦值之前就使用變量等等。


缺點: pychecker 不是完美的。爲了好好利用它,我們有時候得:a) 針對它來寫代碼 b) 禁用特定警告 c) 改進它 d) 或者忽略它。


決定: 確保在代碼上執行 pychecker。


你可以設置一個名爲 __pychecker__ 的模塊級別變量來適當禁用某些警告。比如:


__pychecker__ = 'no-callinit no-classattr'

[Get Code]

用這種方法禁用有個好處:我們可以很容易地找到這些禁用並且重新啓用它們。


你可以用 pychecker --help 來獲得 pychecker 警告的列表。


禁用“未被使用的變量”警告可以用 _ 作爲未使用變量的標識符,或者給變量名添加 unused_ 前綴。有些情況下無法改變變量名,那你可以在函數開頭調用它們一下,比如:


def Foo(a, unused_b, unused_c, d=None, e=None):

  d, e = (d, e)  # silence pychecker

  return a

[Get Code]

最理想的是, pychecker 會竭盡全力保證這樣的“未使用聲明”是真實的。


你可以在PyChecker 主頁找到更多相關信息。


(Edit Section ↓)

2.2 導入模塊和包

是什麼: 在模塊與模塊之間共享代碼的重用機制。


優點: 最簡單同時也是最常用的共用方法。


缺點: from foo import * 或者 from foo import Bar 很噁心而且這會使尋找模塊之間的依賴關係變難,從而導致嚴重的維護問題。


決定: 用 import x 來導入包和模塊。只有在 x 是一個包(package),而 y 是一個模塊(module)的時候才用from x import y 。這可以讓使用者無需說明完整的包前綴就能引用模塊。比如 sound.effects.echo 包可以像下面這樣導入:


from sound.effects import echo

...

echo.echofilter(input, output, delay=0.7, atten=4)

[Get Code]

即使模塊是在同一個包裏,也不要直接導入模塊而不給出完整的包名字。這可能導致包被導入多次以及非預期的副作用。


例外的情況:當且僅當 Bar 是 foo 中的一個頂級 singleton,並且叫做 foo_Bar 是爲了描述 Bar 與 foo 之間的關係,那麼纔可以用 from foo import Bar as foo_Bar 。


比如,像下面這樣是可以的:


from soc.logic.models.user import logic as user_logic

...

user_logic.getForCurrentAccount()

[Get Code]

以下寫法更受歡迎:


from soc.logic import models

...

import soc.logic.models.user

...

models.logic.user.logic.getForCurrentAccount()

[Get Code]

(Edit Section ↓)

2.3 完整路徑導入

是什麼: 每個模塊都通過模塊的完整路徑位置導入和引用。


優點: 避免模塊名字之間的衝突,而且查找模塊更容易。


缺點: 部署代碼會麻煩一些,因爲你必須重現整個包的分層結構。


決定: 所有新代碼都應該使用他們的包名字來引用模塊。不要爲了從其他目錄導入模塊而修改 sys.path 和PATHONPATH。(可能有些情況下仍然需要修改路徑,但只要可能的話就應該避免這樣做。)


應該像下面這樣導入:


# 使用完整名字引用:

import soc.logging


# 僅使用模塊名字引用:

from soc import logging

[Get Code]

有些包名字可能和常用的本地變量名是一樣的。在這種情況下爲了避免混亂,先導入包然後清晰地單獨導入模塊有時候也是有意義的。結果看起來像這樣:


from soc import models

import soc.models.user


...

  user = models.user.User()

[Get Code]

在創建時就避免易引發混亂的模塊命名可能是最佳方案,但有時選擇直觀的模塊名字(比如上面例子中soc.models 中的那些模塊)會導致需要像上面這樣做 workaround 。


(Edit Section ↓)

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

[Get Code]

永遠不要直接捕獲所有異常,也即 except: 語句,或者捕獲 Exception 和 StandardError,除非你會把異常重新拋出或者是在你線程最外層的代碼塊中(而且你會打印一個出錯信息)。在這種意義上來講, Python 是很健壯的,因爲 except: 會真正捕獲包括 Python 語法錯誤在內的所有東西。使用 except: 很容易會掩蓋真正的 Bug 。

在 try/except 塊中使代碼量最小化。try 裏頭的內容越多,就更有可能出現你原先並未預期會拋出異常的代碼拋出異常的情況。在這種情況下,try/except 代碼塊就隱藏了真正的錯誤。

使用 finally 子句執行一段代碼而無論 try 語句塊是否會拋出異常。這在做清除工作的時候經常是很有用的,比如關閉文件。

(Edit Section ↓)

2.5 全局變量

是什麼: 在模塊級別聲明的變量。


優點: 有時很有用。


缺點: 有可能在導入時改變模塊的行爲,因爲在模塊被導入時完成了模塊級別變量的賦值。


決定: 推薦使用類變量而避免使用全局變量。以下是一些例外情況:


作爲腳本中的默認選項。

模塊級別常量,例如 PI = 3.14159 。常量名應該全部使用大寫字母並用下劃線分隔,參考下文命名章節。

有時全局變量對緩存函數返回值或其他需要的運算結果會是挺有用的。

在需要的時候,全局變量應該被用作模塊的內部變量,並通過公開的模塊級函數來訪問。參考下文命名?章節。

(Edit Section ↓)

2.6 內嵌/本地/內部類和函數

是什麼: 類可以定義在函數或另一個類的內部。函數可以定義在另一個函數的內部。內嵌函數可以只讀訪問定義在外部作用域中的變量。


優點: 允許定義只用於一個非常有限的作用域內部的工具類和工具函數,實在太方便了(ADT-y)。


缺點: 內嵌的本地類實例無法被序列化導出(pickle)。


決定: 挺好的,用吧。


(Edit Section ↓)

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]

[Get Code]

Yes:


# 這個例子足夠複雜了,*不應該*使用List Comprehension:

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']

[Get Code]

(Edit Section ↓)

2.8 默認迭代器和運算符

是什麼: 容器類型(比如字典和列表)定義了默認迭代器和從屬關係測試運算符(例如 in 和 not in)。


優點: 默認迭代器和運算符又簡單又高效,它們無需額外方法調用就能直接表達操作。使用默認運算符來編寫函數是一種通用性較強的做法,因爲它可以用於任何支持這些操作的數據類型。


缺點: 你無法通過閱讀方法名字就說出對象的類型(比如說 has_key() 表示一個字典)。這同時也是一個優點。


決定: 在支持它們的類型上使用默認迭代器和運算符,比如列表、字典、和文件。當然這些內置類型同時也定義了迭代器方法。對返回列表的方法傾向於使用迭代器方法,但你不應該在迭代的過程中修改容器中的內容。


Yes:


for key in adict: ...


if key not in adict: ...


if obj in alist: ...


for line in afile: ...


for k, v in dict.iteritems(): ...

[Get Code]

No:


for key in adict.keys(): ...


if not adict.has_key(key): ...


for line in afile.readlines(): ...

[Get Code]

(Edit Section ↓)

2.9 生成器

是什麼: 生成器函數返回一個迭代器,而這個迭代器每次執行 yield 語句生成一個計算結果。每次生成一個計算結果之後,生成器函數的運行時狀態被掛起,直到需要生成下一個計算結果時。


優點: 代碼更簡單。因爲對每次調用來說,本地變量和控制流的狀態都是被保留的。生成器比用計算結果一次性填滿整個列表的函數更節省內存。


缺點: 沒有。


決定: 很好。在生成器函數的 __doc__ String 中使用 "Yields:" 而不是 "Returns:" 。


(Edit Section ↓)

2.10 使用 apply filter map reduce

是什麼: 實用的內置列表操作函數。通常和 lambda 函數一起用。


優點: 代碼很緊湊。


缺點: 高階函數式編程恐怕更難理解。


決定: 對簡單代碼和喜歡把代碼寫成一行的人來說,儘可能用 List Comprehension 而限制使用這些內置函數。一般來說,如果代碼在任何地方長度超過60到80個字符,或者使用了多層函數調用(例如,map(lambda x: x[1], filter(...)))),那這就是一個信號提醒你最好把它重新寫成一個普通的循環。比較:


map/filter:


map(lambda x:x[1], filter(lambda x:x[2] == 5, my_list))

[Get Code]

list comprehensions:


[x[1] for x in my_list if x[2] == 5]

[Get Code]

(Edit Section ↓)

2.11 Lambda functions

是什麼: Lambda 定義僅包含表達式、沒有其他語句的匿名函數,通常用於定義回調或者用於 map() 和 filter()這樣高階函數的運算符。


優點: 方便。


缺點: 比本地函數更難閱讀和調試。沒有名字意味着堆棧追蹤信息更難理解。而且函數只能包含一個表達式,因而表達能力也比較有限。


決定: 對喜歡把代碼寫成一行的人來說沒什麼問題。只要 lambda 函數中的代碼長度超過60到80個字符,那可能把它定義成一個標準(或者嵌套的)函數更合適。


對加法這樣的普通操作,應該使用 operator 模塊中的函數而不是用 lambda 函數。例如,應該用 operator.add 而不是 lambda x, y: x + y。(內置的 sum() 函數其實比這兩者中的任何一個都更合適。)


(Edit Section ↓)

2.12 默認參數值

是什麼: 你可以給函數的參數列表中最靠後的幾個變量指定取值,比如 def foo(a, b=0): 。如果只用一個參數來調用 foo ,b 將被設置爲 0 。如果用兩個參數來調用它, b 將使用第二個參數的值。


優點: 通常你會大量使用函數的默認值,但偶爾會需要覆蓋這些值。默認參數值允許用一種簡單的辦法來做到這一點,而不必爲偶爾的例外情形定義一大堆函數。而且, Python 不支持方法/函數重載,而參數默認值是“模仿”重載行爲的一種簡單方法。


缺點: 默認參數值會在模塊加載的時候被賦值。如果參數是一個列表或者字典之類的可變對象就有可能造成問題。如果函數修改了這個對象(比如在列表最後附加了一個新的項),那默認值也就改變了。


決定: 在以下限制條款下是可以使用的:


不要把可變對象當作函數或方法定義的默認值。

Yes:


def foo(a, b=None):

  if b is None:

    b = []

[Get Code]

No:


def foo(a, b=[]):

  ...

[Get Code]

調用函數的代碼必須對默認參數使用指名賦值(named value)。這多少可以幫助代碼的文檔化,並且當增加新參數進來時幫助避免和發現破壞原有接口。


def foo(a, b=1):

  ...

[Get Code]

Yes:


foo(1)

foo(1, b=2)

[Get Code]

No:


foo(1, 2)

[Get Code]

(Edit Section ↓)

2.13 Properties

是什麼: 在運算量很小時,把對屬性的 get 和 set 方法調用封裝爲標準的屬性訪問方式的一個方法。


優點: 對簡單的屬性訪問來說,去掉直率的 get 和 set 方法調用提高了代碼的可讀性。允許延後計算。考慮到 Pythonic 的方法來維護類的接口。在性能方面,當直接變量訪問是合理的,允許 Properties 就省略了瑣碎的屬性訪問方法,而且將來仍然可以在不破壞接口的情況下重新加入屬性訪問方法。


缺點: Properties 在 getter 和 setter 方法聲明之後生效,也就是要求使用者注意這些方法在代碼很靠後的地方纔能被使用(除了用 @property 描述符創建的只讀屬性之外 —— 見下文詳述)。必須繼承自 object 。會像運算符重載一樣隱藏副作用。對子類來說會很難理解。


決定: 在那些你通常本來會用簡單、輕量的訪問/設置方法的代碼中使用 Properties 來訪問和設置數據。只讀的屬性應該用 @property 描述符?來創建。


如果 Property 自身沒有被覆蓋,那 Properties 的繼承並非顯而易見。因此使用者必須確保訪問方法被間接調用,以便確保子類中被覆蓋了的方法會被 Property 調用(使用“模板方法(Template Method)”設計模式)。


Yes:


import math


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

[Get Code]

== True/False 求值


是什麼: 在布爾型上下文下, 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。

(Edit Section ↓)

2.14 布爾內置類型

是什麼: 從 Python 2.3 開始加入了布爾類型,也即加入了兩個新的內置常量:True 和 False。


優點: 這使代碼更容易閱讀而且與之前版本中使用整數作爲布爾型的做法向後兼容。


缺點: 沒有。


決定: 使用布爾型。


(Edit Section ↓)

2.15 String 方法

是什麼: String 對象包括一些以前是 string 模塊中的函數的方法。


優點: 無需導入 string 模塊,而且這些方法在標準 byte 字符串和 unicode 字符串上都能使用。


缺點: 沒有。


決定: 用吧。 string 模塊已經被廢棄了,現在更推薦使用 String 方法。


No: words = string.split(foo, ':')


Yes: words = foo.split(':')


(Edit Section ↓)

2.16 靜態域

是什麼: 被嵌套的 Python 函數(nested Python function)可以引用定義在容器函數(enclosing function)中的變量,但無法對它們重新賦值。變量綁定依據靜態域(Lexical Scoping)決定,也就是說,基於靜態的程序文本(static program text)。代碼塊中對一個名字的任意賦值都將導致 Python 把對這個名字的所有引用當作本地變量,即使是先調用後賦值。如果存在全局變量聲明,那這個名字就會被當作是全局變量。


以下是使用這一特性的例子:


def getAdder(summand1):

  """getAdder 返回把數字與一個給定數字相加的函數。"""

  def anAdder(summand2):

    return summand1 + summand2


  return anAdder

[Get Code]

優點: 一般會得到更清晰、更優雅的代碼。而且特別符合有經驗的 Lisp 和 Scheme (以及 Haskell、ML 等等)程序員的習慣。


缺點: 沒有。


決定: 可以用。


(Edit Section ↓)

2.17 函數和方法修飾符

是什麼: 在 Python 2.4 版本增加了函數和方法的修飾符(又稱“@ notation”)。最常用的修飾符是@classmethod 和 @staticmethod,用於把普通的方法轉化爲類方法或靜態方法。然而,修飾符語法同樣允許用戶自定義修飾符。具體地,對函數 myDecorator 來說:


class C(object):

  @myDecorator

  def aMethod(self):

    # method body ...

[Get Code]

和如下代碼是等價的:


class C(object):

  def aMethod(self):

    # method body ...

  aMethod = myDecorator(aMethod)

[Get Code]

優點: 能夠優雅地對指定方法進行變形,而且這種變形避免了重複代碼,強化了通用性(enforce invariants)等。


缺點: 修飾符能對函數的參數和返回值進行任意操作,會導致出乎意料之外的隱含行爲。除此之外,修飾符會在導入階段被執行。修飾符代碼中的故障幾乎是不可能被修復的。


決定: 在有明顯好處時使用修飾符是明智的。修飾符應該和函數遵循同樣的導入和命名準則。修飾符的__doc__ string 應該清晰地聲明該函數是一個修飾符,而且要爲修飾符寫單元測試。


避免在修飾符資深引入外部依賴關係(比如,不要依賴文件、 sockets 、數據庫連接等),因爲在修飾符運行時(在導入階段,也許源於 pychecker 或其他工具)這些都有可能是無效的。在所有情況下都應(儘可能)保證使用有效參數調用修飾符時能成功運行。


修飾符是“頂級代碼”(top level code)的一種特例 —— 參考主入口章節的更多討論。


(Edit Section ↓)

2.18 線程

Google App Engine 不支持線程,所以在 SoC framework 和 Melange Web 應用程序中也別用它。


(Edit Section ↓)

2.19 高級特性

是什麼: Python 是一種極爲靈活的語言,提供諸如 metaclass、 bytecode 訪問、即時編譯、動態繼承、 object reparenting、 import hacks、反射、修改系統內部結構等很多很炫的特性。


優點: 這些都是很強大的語言特性,能使你的代碼更緊湊。


缺點: 在並非絕對必要的時候使用這些很“酷”的特性是很誘人的。裏面使用了這些並不常見的特性的代碼會更難讀、更難懂、更難 debug。也許在剛開始的時候好像還沒這麼糟(對代碼的原作者來說),但當你重新回到這些代碼,就會覺得這比長點但簡單直接的代碼要更難搞。


決定: 在 Melange 代碼中避免使用這些特性。例外的情形在開發者郵件列表中討論。


(Edit Section ↓)

3. Python 編碼風格方面的準則

(Edit Section ↓)

3.1 分號

不要用分號作爲你的行結束符,也不要利用分號在同一行放兩個指令。


(Edit Section ↓)

3.2 每行長度

一行最多可以有80個字符。


例外: 導入模塊的行可以超過80個字符再結束。


確保 Python 隱含的連接行(line joining)放在圓括號、方括號或大括號之間。如果需要的話,你可以在表達式兩頭放一堆額外的圓括號。


Yes:


fooBar(self, width, height, color='black', design=None, x='foo',

       emphasis=None, highlight=0)



if ((width == 0) and (height == 0)

    and (color == 'red') and (emphasis == 'strong')):

[Get Code]

當表示文本的字符串(literal string)一行放不下的時候,用圓括號把隱含的連接行括起來。


x = ('This will build a very long long '

     'long long long long long long string')

[Get Code]

注意上例中連續行中元素的縮進,參考縮進章節的解釋。


(Edit Section ↓)

3.3 圓括號

吝嗇地使用圓括號。在以下情況下別用:


在 return 語句中。

在條件判斷語句中。除非是用圓括號來暗示兩行是連在一起的(參見上一節)。

在元組(tuple)周圍。除非因爲語法而不得不加或是爲了顯得更清晰。

但在下列情況下是可以用圓括號的:


暗示兩行是連在一起的。

用來括起長表達式中的子表達式(包括子表達式是條件判斷語句一部分的情形)。

實際上,在子表達式周圍用圓括號比單純依賴運算符優先級要好。


Yes:


if foo:


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()

[Get Code]

No:


if (x):


while (x):


if not(x):


if ((x < 3) and (not y)):


return (foo)


for (x, y) in dict.items():


(x, (y, z)) = funcThatReturnsNestedTuples()

[Get Code]

(Edit Section ↓)

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)

[Get Code]

No:


# 不應該在首行塞東西:

foo = longFunctionName(var_one, var_two,

    var_three, var_four)


# 不應該用兩空格的懸掛縮進:

foo = longFunctionName(

  var_one, var_two, var_three,

  var_four)

[Get Code]

(Edit Section ↓)

3.5 空行

在頂級定義(可以是函數或者類定義)之間加兩個空行。在方法定義之間以及“ class ”那一行與第一個方法之間加一個空行。在__doc__ string和它後面的代碼之間加一個空行。在函數和方法內部你認爲合適的地方加一個空行。


在文件最後總是加一個空行,這可以避免很多 diff 工具生成“No newline at end of file”信息。


(Edit Section ↓)

3.6 空格

在圓括號、方括號、大括號裏面不要加空格。


Yes: spam(ham[1], {eggs: 2}, [])


No: spam( ham[ 1 ], { eggs: 2 }, [ ] )


在逗號、分號、冒號前面不要加空格。逗號、分號、冒號後面必須加空格,除非那是行尾。


Yes:


if x == 4:


print x, y


x, y = y, x

[Get Code]

No:


if x == 4 :


print x , y


x , y = y , x

[Get Code]

在表示參數、列表、下標、分塊開始的圓括號/方括號前面不要加空格。


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)


(Edit Section ↓)

3.7 Python 解釋器

模塊開頭應該是一個“shebang”行,用於指定執行此程序的 Python 解釋器:


#!/usr/bin/python2.5

[Get Code]

Google App Engine 要求使用 Python 2.5。


(Edit Section ↓)

3.8 註釋

(Edit Section ↓)

Doc strings

Python 有一種獨特的註釋風格稱爲 __doc__ String。包、模塊、類、函數的第一個語句如果是字符串那麼就是一個 __doc__ String。這種字符串可以用對象的 __doc__() 成員函數自動提取,而且會被用於 pydoc。(試着在你的模塊上運行 pydoc 來看看它是怎麼工作的。)我們對 __doc__ String 的約定是:用三個雙引號把字符串括起來。 __doc__ String 應該這樣組織:首先是一個以句號結束的摘要行(實實在在的一行,不多於80個字符),然後接一個空行,再接 __doc__ String 中的其他內容,並且這些文字的起始位置要和首行的第一個雙引號在同一列。下面幾節是關於 __doc__ String 格式更進一步的原則


(Edit Section ↓)

模塊

每個文件開頭都應該包含一個帶有版權信息和許可聲明的塊註釋。


(Edit Section ↓)

版權和許可聲明


#!/usr/bin/python2.5

#

# 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.

[Get Code]

然後接着寫描述模塊內容的__doc__ String,其中作者信息應該緊跟着許可聲明。如果還給出了作者郵件地址,那添加到 __authors__ 列表的整個作者字符串應該就是一個與 RFC 2821 兼容的電子郵件地址。


(Edit Section ↓)

模塊頭及作者信息


"""用一行文字概述模塊或腳本,用句號結尾。


留一個空行。本 __doc__ string 的其他部分應該包括模塊或腳本的全面描述。作爲可選項,還可以包括導出的類和函數的簡要描述。


  ClassFoo: 一行概述。

  functionBar(): 一行概述。

"""


__authors__ = [

  # 請按照姓氏字母順序排列:

  '"John Smith" <[email protected]>',

  '"Joe Paranoid" <[email protected]>',  # 應提供電子郵件地址

]

[Get Code]

如果新的貢獻者還沒添加到AUTHORS file中,那應該在該貢獻者第一次向版本控制工具提交代碼時同時把他/她添加到這個文件裏。


(Edit Section ↓)

函數和方法

如果不是用途非常明顯而且非常短的話,所有函數和方法都應該有 __doc__ string 。此外,所有外部能訪問的函數和方法,無論有多短、有多簡單,都應該有 __doc__ string 。 __doc__ string 應該包括函數能做什麼、輸入數據的具體描述(“Args:”)、輸出數據的具體描述(“Returns:”、“Raises:”、或者“Yields:”)。 __doc__ string 應該能提供調用此函數相關的足夠信息,而無需讓使用者看函數的實現代碼。如果參數要求特定的數據類型或者設置了參數默認值,那 __doc__ string 應該明確說明這兩點。“Raises:”部分應該列出此函數可能拋出的所有異常。生成器函數的 __doc__ string 應該用“Yields:”而不是Returns:。


函數和方法的 __doc__ string 一般不應該描述實現細節,除非其中涉及非常複雜的算法。在難以理解的代碼中使用塊註釋或行內註釋會是更合適的做法。


def fetchRows(table, keys):

  """取出表中的多行內容。


  Args:

    table: 打開的表。 Table 類的實例。

    keys: 字符串序列,表示要從表中取出的行的鍵值。


  Returns:

    一個字典,映射指定鍵值與取出的表中對應行的數據:


    {'Serak': ('Rigel VII', 'Preparer'),

     'Zim': ('Irk', 'Invader'),

     'Lrrr': ('Omicron Persei 8', 'Emperor')}


    如果 keys 參數中的鍵值沒有出現在字典裏,就表示對應行在表中沒找到。


  Raises:

    IOError: 訪問 table.Table 對象時發生的錯誤。

  """

  pass

[Get Code]

(Edit Section ↓)

類應該在描述它的類定義下面放 __doc__ string 。如果你的類有公開屬性值,那應該在 __doc__ string 的Attributes: 部分寫清楚。


class SampleClass(object):


  """這裏是類的概述。


  詳細的描述信息……

  詳細的描述信息……


  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

[Get Code]

(Edit Section ↓)

塊註釋及行內註釋

加註釋的最後一個位置是在難以理解的代碼裏面。如果你打算在下一次代碼複查(code review)的時候解釋這是什麼意思,那你應該現在就把它寫成註釋。在開始進行操作之前,就應該給複雜的操作寫幾行註釋。對不直觀的代碼則應該在行末寫註釋。


# 我們用帶權的字典檢索來查找 i 在數組中的位置。我們根據數組中最大的數和

# 數組的長度來推斷可能的位置,然後做二分法檢索找到準確的值。


if i & (i-1) == 0:        # 當且僅當 i 是 2 的冪時,值爲 true

[Get Code]

這些註釋應該和代碼分開才更易讀。在塊註釋值錢應該加一個空行。一般行末註釋應該和代碼之間至少空兩個格。如果連續幾行都有行末註釋(或者是在一個函數中),可以把它們縱向對齊,但這不是必須的。


另一方面,不要重複描述代碼做的事。假設正在讀代碼的人比你更懂 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.


# *不好的註釋*:現在要遍歷數組 b 並且確保任何時候 i 出現時,下一個元素都是 i+1

[Get Code]

(Edit Section ↓)

3.9 類

如果不從其他基類繼承,那就應該明確地從 object 基類繼承。這一條對嵌套類也適用。


No:


class SampleClass:

  pass


class OuterClass:

  class InnerClass:

    pass

[Get Code]

Yes:


class SampleClass(object):

  pass


class OuterClass(object):

  class InnerClass(object):

    pass


class ChildClass(ParentClass):

  """已經從另一個類顯式繼承了。"""

  pass

[Get Code]

從 object 繼承是爲了讓類屬性能夠正常工作,這會避免我們一旦切換到 Python 3000 時,打破已經習慣了的特有風格。同時這也定義了一些特殊函數,來實現對象(object)的默認語義,包括:__new__、__init__、__delattr__、__getattribute__、__setattr__、__hash__、__repr__、和 __str__。


(Edit Section ↓)

3.10 字符串

應該用 % 運算符來格式化字符串,即使所有的參數都是字符串。不過你也可以在 + 和 % 之間做出你自己最明智的判斷。


No:


x = '%s%s' % (a, b)  # 這種情況下應該用 + 

x = imperative + ', ' + expletive + '!'

x = 'name: ' + name + '; score: ' + str(n)

[Get Code]

Yes:


x = a + b

x = '%s, %s!' % (imperative, expletive)

x = 'name: %s; score: %d' % (name, n)

[Get Code]

應該避免在循環中用 + 或 += 來連續拼接字符串。因爲字符串是不變型,這會毫無必要地建立很多臨時對象,從而二次方級別的運算量而不是線性運算時間。相反,應該把每個子串放到 list 裏面,然後在循環結束的時候用''.join() 拼接這個列表。


No:


employee_table = '<table>'

for last_name, first_name in employee_list:

  employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)

employee_table += '</table>'

[Get Code]

Yes:


items = ['<table>']

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)

[Get Code]

對多行字符串使用 """ 而不是 '''。但是注意,通常使用隱含的行連接(implicit line joining)會更清晰,因爲多行字符串不符合程序其他部分的縮進風格。


No:


print """這種風格非常噁心。

不要這麼用。

"""

[Get Code]

Yes:


print ("這樣會好得多。\n"

           "所以應該這樣用。\n")

[Get Code]

(Edit Section ↓)

3.11 TODO style

在代碼中使用 TODO 註釋是臨時的、短期的解決方案,或者說是足夠好但不夠完美的辦法。


TODO 應該包括全部大寫的字符串 TODO ,緊接用圓括號括起來的你的用戶名: TODO(username) 。其中冒號是可選的。主要目的是希望有一種一致的 TODO 格式,而且可以通過用戶名檢索。


# TODO(someuser): 這裏應該用 "*" 來做級聯操作。

# TODO(anotheruser) 用 relations 來修改這兒。

[Get Code]

如果你的 TODO 是“在未來某個時間做某事”的形式,確保你要麼包括非常確定的日期(“Fix by November 2008”)或者非常特殊的事件(“在數據庫中的 Foo 實體都加上新的 fubar 屬性之後刪除這段代碼。”)。


(Edit Section ↓)

3.12 import 分組及順序

應該在不同的行中做 import :


Yes:


import sys

import os

[Get Code]

No:


import sys, os

[Get Code]

import 總是放在文件開頭的,也即在所有模塊註釋和 __doc__ string 的後面,在模塊全局變量及常量的前面。import 應該按照從最常用到最不常用的順序分組放置:


import 標準庫

import 第三方庫

import Google App Engine 相關庫

import Django 框架相關庫

import SoC framework 相關庫

import 基於 SoC 框架的模塊

import 應用程序特有的內容

應該按照字母順序排序,但所有以 from ... 開頭的行都應該靠前,然後是一個空行,再然後是所有以 import ... 開頭的行。以 import ... 開頭的標準庫和第三方庫的 import 應該放在最前面,而且和其他分組隔開:


import a_standard

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

[Get Code]

在 import/from 行中,語句應該按照字母順序排序:


from a import f

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

[Get Code]

(Edit Section ↓)

3.13 語句

一般一行只放一個語句。但你可以把測試和測試結果放在一行裏面,只要這樣做合適。具體來說,你不能這樣寫try/except ,因爲 try 和 except 不適合這樣,你只可以對不帶 else 的 if 這麼幹。


Yes:


if foo: fuBar(foo)

[Get Code]

No:


if foo: fuBar(foo)

else:   fuBaz(foo)


try:               fuBar(foo)

except ValueError: fuBaz(foo)


try:

  fuBar(foo)

except ValueError: fuBaz(foo)

[Get Code]

(Edit Section ↓)

3.14 訪問控制

如果存取器函數很簡單,那你應該用公開的變量來代替它,以避免 Python 中函數調用的額外消耗。在更多功能被加進來時,你可以用 Property 特性來保持語法一致性。


另一方面,如果訪問更復雜,或者訪問變量的成本較爲顯著,那你應該用函數調用(遵循命名準則),比如getFoo() 或者 setFoo()。如果過去的行爲允許通過 Property 訪問,那就別把新的存取器函數綁定到 Property 上。所有仍然企圖用舊方法訪問變量的代碼應該是非常顯眼的,這樣調用者會被提醒這種改變是比較複雜的。


(Edit Section ↓)

3.15 命名

(Edit Section ↓)

要避免的命名方式

使用單個字符命名,除非是計數器或迭代器。

在任何包或模塊的名字裏面使用減號。

__double_leading_and_trailing_underscore__ 在變量開頭和結尾都使用兩個下劃線(在 Python 內部有特殊含義)。

(Edit Section ↓)

命名約定

注意這裏某些命名約定和PEP8不一樣,而是遵循“Google Python 編碼風格指南”的原始版本,也即本編碼風格指南的起源。


“Internal”表示模塊內部或類中的保護域(protected)和私有域。

變量名開頭加一個下劃線(_)能對保護模塊中的變量及函數提供一些支持(不會被 import * from 導入)。

在實例的變量和方法開頭加兩個下劃線(__)能有效地幫助把該變量或方法變成類的私有內容(using name mangling)。

把模塊中相關的類和頂級函數放在一起。不像 Java ,這裏無需要求自己在每個模塊中只放一個類。但要確保放在同一模塊中的類和頂級函數是高內聚的。

對類名使用駝峯式(形如 CapWords),而對模塊名使用下劃線分隔的小寫字母(lower_with_under.py)。

(Edit Section ↓)

命名樣例

類別 公開的 內部的

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 之後可以用來把屬性訪問轉化爲函數調用而無需改變訪問語法。


(Edit Section ↓)

3.16 程序入口

所有的 Python 源代碼文件都應該是可導入的。在 Python 中,pychecker、 pydoc、 以及單元測試都要求模塊是可導入的。你的代碼應該總是在執行你的主程序之前檢查if __name__ == '__main__': ,這樣就不會在模塊被導入時執行主程序。大寫 main() 是故意要和命名約定的其他部分不一致,也就是說建議寫成 Main() 。


if __name__ == '__main__':

  # 參數解析

  main()

[Get Code]

在模塊被導入時,所有頂級縮進代碼會被執行。注意不要調用函數、創建對象、或者做其他在文件被 pycheck 或pydoc 的時候不應該做的操作。


(Edit Section ↓)

3.17 總結

要一致


如果你在編寫代碼,花幾分鐘看看你周圍的代碼並且弄清它的風格。如果在 if 語句周圍使用了空格,那你也應該這樣做。如果註釋是用星號組成的小盒子圍起來的,那你同樣也應該這樣寫。


編碼風格原則的關鍵在於有一個編程的通用詞彙表,這樣人們可以人民可以集中到你要說什麼而不是你要怎麼說。我們在這 裏提供全局的編碼風格規則以便人們知道這些詞彙,但局部風格也重要。如果你加入文件中的代碼看起來和裏面已經有的代碼截然不同,那在讀者讀它的時候就會被 破壞節奏。儘量避免這樣。



from:


ref:http://www.elias.cn/Python/PythonStyleGuide?from=Develop.PythonStyleGuide


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章