Python開發規範

 

背景

Python 是公司最主要的腳本語言,這個代碼規範主要包含的是針對python的編程準則。html

Python語言規範

Lint

對你的代碼運行pylintnode

定義:

pylint是一個在Python源代碼中查找bug的工具。對於C和C++這樣的不那麼動態的語言,這些bug一般由編譯器來捕獲。因爲Python的動態特性,有些警告可能不對,不過僞告警應該不多。python

優勢:

能夠捕獲容易忽視的錯誤,例如輸入錯誤,使用未賦值的變量等。程序員

缺點: 

Pylint並不完美,要利用其優點,咱們有時侯須要:a) 圍繞着它來寫代碼 b) 抑制其告警 c) 改進它,或者d) 忽略它。算法

結論:

確保對你的代碼運行pylint,抑制不許確的警告,以便可以將其餘警告暴露出來。你能夠經過設置一個行註釋來抑制告警。例如:數據庫

dict = 'something awful'  # Bad Idea... pylint: disable=redefined-builtin

pylint警告是以一個數字編號(如 C0112 )和一個符號名(如 empty-docstring )來標識的。在編寫新代碼或更新已有代碼時對告警進行醫治,推薦使用符號名來標識。若是警告的符號名不夠見名知意,那麼請對其增長一個詳細解釋。express

採用這種抑制方式的好處是咱們能夠輕鬆查找抑制並回顧它們。編程

你可使用命令 pylint --list-msgs 來獲取pylint告警列表。可使用命令pylint --help-msg=C6409 ,以獲取關於特定消息的更多信息。c#

相比較於以前使用的 pylint: disable-msg ,本文推薦使用 pylint: disable 。設計模式

要抑制」參數未使用」告警,你能夠用」_」做爲參數標識符,或者在參數名前加」unused_」。遇到不能改變參數名的狀況,你能夠經過在函數開頭」提到」它們來消除告警。例如:

def foo(a, unused_b, unused_c, d=None, e=None):
    _ = d, e
    return a

導入

僅對包和模塊使用導入

定義:

模塊間共享代碼的重用機制。

優勢:

命名空間管理約定十分簡單,每一個標識符的源都用一種一致的方式指示,x.Obj表示Obj對象定義在模塊x中。

缺點:

模塊名仍可能衝突,有些模塊名太長,不太方便。

結論:

使用 import x 來導入包和模塊。

使用 from x import y ,其中x是包前綴,y是不帶前綴的模塊名。

使用 from x import y as z,若是兩個要導入的模塊都叫作z或者y太長了。

例如,模塊 sound.effects.echo 能夠用以下方式導入:

from sound.effects import echo
...
echo.EchoFilter(input, output, delay=0.7, atten=4)

導入時不要使用相對名稱,即便模塊在同一個包中,也要使用完整包名,這能幫助你避免無心間導入一個包兩次。

使用模塊的全路徑名來導入每一個模塊

優勢:

避免模塊名衝突,查找包更容易。

缺點:

部署代碼變難,由於你必須複製包層次。

結論:

全部的新代碼都應該用完整包名來導入每一個模塊。

應該像下面這樣導入:

# Reference in code with complete name.
import sound.effects.echo
 
# Reference in code with just module name (preferred).
from sound.effects import echo

異常

容許使用異常,但必須當心

定義:

異常是一種跳出代碼塊的正常控制流來處理錯誤或者其它異常條件的方式。

優勢:

正常操做代碼的控制流不會和錯誤處理代碼混在一塊兒,當某種條件發生時,它也容許控制流跳過多個框架。例如,一步跳出N個嵌套的函數,而沒必要繼續執行錯誤的代碼。

缺點:

可能會致使讓人困惑的控制流,調用庫時容易錯過錯誤狀況。

結論:

異常必須遵照特定條件:

  1. 像這樣觸發異常:raise MyException("Error message") 或者 raise MyException ,不要使用兩個參數的形式( raise MyException, "Error message" )或者過期的字符串異常( raise "Error message" )。
  1. 模塊或包應該定義本身的特定域的異常基類,這個基類應該從內建的Exception類繼承,模塊的異常基類應該叫作」Error」。
  2. 當捕獲異常時,使用 as 而不要用逗號。例如
3.  class Error(Exception):
    pass
  1. 永遠不要使用 except: 語句來捕獲全部異常,也不要捕獲 Exception 或者 StandardError ,除非你打算從新觸發該異常,或者你已經在當前線程的最外層(記得仍是要打印一條錯誤消息)。在異常這方面,Python很是寬容,except: 真的會捕獲包括Python語法錯誤在內的任何錯誤,使用except: 很容易隱藏真正的bug。
  2. 儘可能減小try/except塊中的代碼量,try塊的體積越大,指望以外的異常就越容易被觸發。這種狀況下,try/except塊將隱藏真正的錯誤。
  3. 使用finally子句來執行那些不管try塊中有沒有異常都應該被執行的代碼,這對於清理資源經常頗有用,例如關閉文件。
8.  try:
9.      raise Error
10.except Error as error:
    pass

全局變量

避免全局變量

定義:

定義在模塊級的變量。

優勢:

偶爾有用。

缺點:

導入時可能改變模塊行爲,由於導入模塊時會對模塊級變量賦值。

結論:

避免使用全局變量,用類變量來代替,但也有一些例外:

  1. 腳本的默認選項。
  2. 模塊級常量,例如:PI = 3.14159,常量應該全大寫,用下劃線鏈接。
  3. 有時候用全局變量來緩存值或者做爲函數返回值頗有用。
  4. 若是須要,全局變量應該僅在模塊內部可用,並經過模塊級的公共函數來訪問。

嵌套/局部/內部類或函數

鼓勵使用嵌套/本地/內部類或函數

定義:

類能夠定義在方法,函數或者類中;函數能夠定義在方法或函數中;封閉區間中定義的變量對嵌套函數是隻讀的。

優勢:

容許定義僅用於有效範圍的工具類和函數。

缺點:

嵌套類或局部類的實例不能序列化(pickled)。

結論:

推薦使用。

列表推導(List Comprehensions)

能夠在簡單狀況下使用

定義:

列表推導(list comprehensions)與生成器表達式(generator expression)提供了一種簡潔高效的方式來建立列表和迭代器,而沒必要藉助map(),filter(),或者lambda。

優勢:

簡單的列表推導能夠比其它的列表建立方法更加清晰簡單。生成器表達式能夠十分高效,由於它們避免了建立整個列表。

缺點:

複雜的列表推導或者生成器表達式可能難以閱讀。

結論:

適用於簡單狀況,每一個部分應該單獨置於一行:映射表達式,for語句,過濾器表達式。禁止多重for語句或過濾器表達式,複雜狀況下仍是使用循環。

Yes:
  result = []
  for x in range(10):
      for y in range(5):
          if x * y > 10:
              result.append((x, y))
 
  for x in xrange(5):
      for y in xrange(5):
          if x != y:
              for z in xrange(5):
                  if y != z:
                      yield (x, y, z)
 
  return ((x, complicated_transform(x))
          for x in long_generator_function(parameter)
          if x is not None)
 
  squares = [x * x for x in range(10)]
 
  eat(jelly_bean for jelly_bean in jelly_beans
      if jelly_bean.color == 'black')
 
No:
  result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]
 
  return ((x, y, z)
          for x in xrange(5)
          for y in xrange(5)
          if x != y
          for z in xrange(5)
          if y != z)

默認迭代器和操做符

若是類型支持,就使用默認迭代器和操做符,好比列表,字典及文件等。

定義:

容器類型,像字典和列表,定義了默認的迭代器和關係測試操做符(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(): ...
No:   for key in adict.keys(): ...
      if not adict.has_key(key): ...
      for line in afile.readlines(): ...

生成器

按需使用生成器。

定義:

所謂生成器函數,就是每當它執行一次生成(yield)語句,它就返回一個迭代器,這個迭代器生成一個值。生成值後,生成器函數的運行狀態將被掛起,直到下一次生成。

優勢:

簡化代碼,由於每次調用時,局部變量和控制流的狀態都會被保存。比起一次建立一系列值的函數,生成器使用的內存更少

缺點:

沒有。

結論:

鼓勵使用,注意在生成器函數的文檔字符串中使用」Yields:」而不是」Returns:」。

Lambda 函數

適用於單行函數

定義:

與語句相反,lambda在一個表達式中定義匿名函數,經常使用於爲 map() 和 filter() 之類的高階函數定義回調函數或者操做符。

優勢:

方便。

缺點:

比本地函數更難閱讀和調試,沒有函數名意味着堆棧跟蹤更難理解。因爲lambda函數一般只包含一個表達式,所以其表達能力有限。

結論:

適用於單行函數,若是代碼超過60-80個字符,最好仍是定義成常規(嵌套)函數。

對於常見的操做符,例如乘法操做符,使用 operator 模塊中的函數以代替lambda函數。例如,推薦使用 operator.mul ,而不是 lambda x, y: x * y 。

條件表達式

適用於單行函數

定義:

條件表達式是對於if語句的一種更爲簡短的句法規則。例如:x = 1 if cond else 2 。

優勢:

比if語句更加簡短和方便。

缺點:

比if語句難於閱讀,若是表達式很長,難於定位條件。

結論:

適用於單行函數,在其餘狀況下,推薦使用完整的if語句。

默認參數值

適用於大部分狀況。

定義:

你能夠在函數參數列表的最後指定變量的值,例如,def foo(a, b = 0): 。若是調用foo時只帶一個參數,則b被設爲0。若是帶兩個參數,則b的值等於第二個參數。

優勢:

你常常會碰到一些使用大量默認值的函數,但偶爾(比較少見)你想要覆蓋這些默認值。默認參數值提供了一種簡單的方法來完成這件事,你不須要爲這些罕見的例外定義大量函數。同時,Python也不支持重載方法和函數,默認參數是一種」仿造」重載行爲的簡單方式。

缺點:

默認參數只在模塊加載時求值一次,若是參數是列表或字典之類的可變類型,這可能會致使問題。若是函數修改了對象(例如向列表追加項),默認值就被修改了。

結論:

鼓勵使用,不過注意不要在函數或方法定義中使用可變對象做爲默認值。

Yes: def foo(a, b=None):
         if b is None:
             b = []
No:  def foo(a, b=[]):
         ...
No:  def foo(a, b=time.time()):  # The time the module was loaded???
         ...
No:  def foo(a, b=FLAGS.my_thing):  # sys.argv has not yet been parsed...

屬性(properties)

訪問和設置數據成員時,你一般會使用簡單,輕量級的訪問和設置函數,建議用屬性(properties)來代替它們。

定義:

一種用於包裝方法調用的方式,當運算量不大,它是獲取和設置屬性(attribute)的標準方式。

優勢:

經過消除簡單的屬性(attribute)訪問時顯式的get和set方法調用,可讀性提升了,用Pythonic的方式來維護類的接口,就性能而言,當直接訪問變量是合理的,添加訪問方法就顯得瑣碎而無心義。使用屬性(properties)能夠繞過這個問題,未來也能夠在不破壞接口的狀況下將訪問方法加上。

缺點:

屬性(properties)是在get和set方法聲明後指定,這須要使用者在接下來的代碼中注意:set和get是用於屬性(properties)的(除了用 @property 裝飾器建立的只讀屬性)必須繼承自object類,這樣可能隱藏好比操做符重載之類的反作用,繼承時可能會讓人困惑。

結論:

你一般習慣於使用訪問或設置方法來訪問或設置數據,它們簡單而輕量,不過咱們建議你在新的代碼中使用屬性,只讀屬性應該用 @property 裝飾器來建立。

若是子類沒有覆蓋屬性,那麼屬性的繼承可能看上去不明顯,所以使用者必須確保訪問方法間接被調用,以保證子類中的重載方法被屬性調用(使用模板方法設計模式)。

Yes: import math
 
     class Square(object):
         """A square with two properties: a writable area and a read-only perimeter.
         To use:
         >>> 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 __get_area(self):
             """Calculates the 'area' property."""
             return self.side ** 2
 
         def ___get_area(self):
             """Indirect accessor for 'area' property."""
             return self.__get_area()
 
         def __set_area(self, area):
             """Sets the 'area' property."""
             self.side = math.sqrt(area)
 
         def ___set_area(self, area):
             """Indirect setter for 'area' property."""
             self._SetArea(area)
 
         area = property(___get_area, ___set_area,
                         doc="""Gets or sets the area of the square.""")
 
         @property
         def perimeter(self):
             return self.side * 4

True/False 的求值

儘量使用隱式false

定義:

Python在布爾上下文中會將某些值求值爲false,按簡單的直覺來說,就是全部的」空」值都被認爲是false,所以0, None,[],{},「」 都被認爲是false。

優勢:

使用Python布爾值的條件語句更易讀也更不易犯錯,大部分狀況下也更快。

缺點:

對C/C++開發人員來講,可能看起來有點怪。

結論:

儘量使用隱式的False,例如:使用 if foo: 而不是 if foo != []: ,不過仍是有一些注意事項須要你銘記在心:

  1. 永遠不要用==或者!=來比較單件,好比None,使用is或者is not。
  2. 注意,當你寫下 if x: 時,你其實表示的是 if x is not None ,例如:當你要測試一個默認值是None的變量或參數是否被設爲其它值,這個值在布爾語義下多是false!
  3. 永遠不要用==將一個布爾量與false相比較,使用 if not x: 代替。若是你須要區分false和None,你應該用像 if not x and x is not None: 這樣的語句。
  4. 對於序列(字符串,列表,元組),要注意空序列是false,所以 if not seq: 或者 if seq: 比if len(seq): 或 if not len(seq): 要更好。
  1. 處理整數時,使用隱式false可能會得不償失(即不當心將None當作0來處理),你能夠將一個已知是整型(且不是len()的返回結果)的值與0比較。
6.  Yes: if not users:
7.           print 'no users'
8.   
9.       if foo == 0:
10.         self.handle_zero()
11. 
12.     if i % 10 == 0:
13.         self.handle_multiple_of_ten()
14.No:  if len(users) == 0:
15.         print 'no users'
16. 
17.     if foo is not None and not foo:
18.         self.handle_zero()
19. 
20.     if not i % 10:
         self.handle_multiple_of_ten()
  1. 注意‘0’(字符串)會被當作true。

過期的語言特性

儘量使用字符串方法取代字符串模塊,使用函數調用語法取代apply(),使用列表推導,for循環取代filter(),map()以及reduce()。

定義:

當前版本的Python提供了你們一般更喜歡的替代品。

結論:

咱們不使用不支持這些特性的Python版本,因此沒理由不用新的方式。

Yes: words = foo.split(':')
 
     [x[1] for x in my_list if x[2] == 5]
 
     map(math.sqrt, data)    # Ok. No inlined lambda expression.
 
     fn(*args, **kwargs)
 
No:  words = string.split(foo, ':')
 
     map(lambda x: x[1], filter(lambda x: x[2] == 5, my_list))
 
     apply(fn, args, kwargs)

詞法做用域(Lexical Scoping)

推薦使用

定義:

嵌套的Python函數能夠引用外層函數中定義的變量,可是不可以對它們賦值。變量綁定的解析是使用詞法做用域,也就是基於靜態的程序文本,對一個塊中的某個名稱的任何賦值都會致使Python將對該名稱的所有引用當作局部變量,甚至是賦值前的處理。若是碰到global聲明,該名稱就會被視做全局變量。

一個使用這個特性的例子:

def get_adder(summand1):
    """Returns a function that adds numbers to a given number."""
    def adder(summand2):
        return summand1 + summand2
 
    return adder
優勢:

一般能夠帶來更加清晰,優雅的代碼,尤爲會讓有經驗的Lisp和Scheme(還有Haskell,ML等)程序員感到欣慰。

缺點:

可能致使讓人迷惑的bug,例以下面這個依據 PEP-0227 的例子:

i = 4
def foo(x):
    def bar():
        print i,
    # ...
    # A bunch of code here
    # ...
    for i in x:  # Ah, i *is* local to Foo, so this is what Bar sees
        print i,
    bar()

所以 foo([1, 2, 3]) 會打印 1 2 3 3 , 不是 1 2 3 4 。

(x是一個列表,for循環實際上是將x中的值依次賦給i。這樣對i的賦值就隱式的發生了,整個foo函數體中的i都會被當作局部變量,包括bar()中的那個,這一點與C++之類的靜態語言仍是有很大差異的)。

結論:

鼓勵使用。

函數與方法裝飾器

若是好處很顯然,就明智而謹慎的使用裝飾器

定義:

用於函數及方法的裝飾器(也就是@標記),最多見的裝飾器是@classmethod 和@staticmethod,用於將常規函數轉換成類方法或靜態方法。不過,裝飾器語法也容許用戶自定義裝飾器。特別地,對於某個函數 my_decorator ,下面的兩段代碼是等效的:

class C(object):
    @my_decorator
    def method(self):
        # method body ...
class C(object):
    def method(self):
        # method body ...
    method = my_decorator(method)
優勢:

優雅的在函數上指定一些轉換,該轉換可能減小一些重複代碼,保持已有函數不變(enforce invariants)等。

缺點:

裝飾器能夠在函數的參數或返回值上執行任何操做,這可能致使讓人驚異的隱藏行爲,並且,裝飾器在導入時執行,從裝飾器代碼的失敗中恢復更加不可能。

結論:

若是好處很明顯,就明智而謹慎的使用裝飾器。裝飾器應該遵照和函數同樣的導入和命名規則,裝飾器的python文檔應該清晰的說明該函數是一個裝飾器。請爲裝飾器編寫單元測試。

避免裝飾器自身對外界的依賴(即不要依賴於文件、socket、數據庫鏈接等),由於裝飾器運行時這些資源可能不可用(由 pydoc 或其它工具導入),應該保證一個用有效參數調用的裝飾器在全部狀況下都是成功的。

裝飾器是一種特殊形式的」頂級代碼」,參考後面關於 Main 的章節。

線程

不要依賴內建類型的原子性。

雖然Python的內建類型例如字典看上去擁有原子操做,可是在某些情形下它們仍然不是原子的(即若是__hash__或__eq__被實現爲Python方法)且它們的原子性是靠不住的,你也不能期望原子變量賦值(由於這個反過來依賴字典)。

優先使用Queue模塊的 Queue 數據類型做爲線程間的數據通訊方式,另外,使用threading模塊及其鎖原語(locking primitives),瞭解條件變量的合適使用方式,這樣你就可使用 threading.Condition 來取代低級別的鎖了。

威力過大的特性

避免使用這些特性

定義:

Python是一種異常靈活的語言,它爲你提供了不少花哨的特性,諸如元類(metaclasses)、字節碼訪問、任意編譯(on-the-fly compilation)、動態繼承、對象父類重定義(object reparenting)、導入黑客(import hacks)、反射、系統內修改(modification of system internals)等等。

優勢:

強大的語言特性,能讓你的代碼更緊湊。

缺點:

使用這些很」酷」的特性十分誘人,但不是絕對必要,使用奇技淫巧的代碼將更加難以閱讀和調試。開始可能還好(對原做者而言),但當你回顧代碼,它們可能會比那些稍長一點可是很直接的代碼更加難以理解。

結論:

在你的代碼中避免這些特性。

Python風格規範

分號

不要在行尾加分號,也不要用分號將兩條命令放在同一行。

行長度

每行不超過80個字符

例外:

  1. 長的導入模塊語句
  2. 註釋裏的URL

不要使用反斜槓換行

Python會將圓括號,中括號和花括號中的行隱式的鏈接起來,你能夠利用這個特色,若是須要,你能夠在表達式外圍增長一對額外的圓括號。

Yes: foo_bar(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'):

若是一個文本字符串在一行放不下,可使用圓括號來實現隱式行鏈接:

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

在註釋中,若是必要,將長的URL放在一行上。

Yes: # See details at
     # http://www.example.com/documentation/csv_file_name_extension_full_specif.html
No:  # See details at
     # http://www.example.com/documentation/\
     # csv_file_name_extension_full_specif.html

注意上面例子中的元素縮進; 你能夠在本文的縮進章節找到解釋。

括號

寧缺毋濫的使用括號

除非是用於實現行鏈接,不然不要在返回語句或條件語句中使用括號,不過在元組兩邊使用括號是能夠的。

Yes: if foo:
         bar()
     while x:
         x = bar()
     if x and y:
         bar()
     if not x:
         bar()
     return foo
     for (x, y) in dict.items(): ...
 
No:  if (x):
         bar()
     if not(x):
         bar()
     return (foo)

縮進

用4個空格來縮進代碼

絕對不要用tab,也不要tab和空格混用,對於行鏈接的狀況,你應該要麼垂直對齊換行的元素(見「行長度」章節的示例),或者使用4空格的懸掛式縮進(這時第一行不該該有參數):

Yes:   # Aligned with opening delimiter
       foo = long_function_name(var_one, var_two,
                                var_three, var_four)
 
       # Aligned with opening delimiter in a dictionary
       foo = {
           long_dictionary_key: value1 +
                                value2,
           ...
       }
 
       # 4-space hanging indent; nothing on first line
       foo = long_function_name(
           var_one, var_two, var_three,
           var_four)
 
       # 4-space hanging indent in a dictionary
       foo = {
           long_dictionary_key:
               long_dictionary_value,
           ...
       }
 
No:   # Stuff on first line forbidden
      foo = long_function_name(var_one, var_two,
          var_three, var_four)
 
      # 2-space hanging indent forbidden
      foo = long_function_name(
        var_one, var_two, var_three,
        var_four)
 
      # No hanging indent in a dictionary
      foo = {
          long_dictionary_key:
              long_dictionary_value,
              ...
      }

空行

頂級定義之間空兩行,方法定義之間空一行

頂級定義之間空兩行,好比函數或者類定義。方法定義,類定義與第一個方法之間,都應該空一行,函數或方法中,某些地方要是你以爲合適,就空一行。

空格

按照標準的排版規範來使用標點兩邊的空格

括號內不要有空格。

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

參數列表,索引或切片的左括號前不該加空格。

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)

不要用空格來垂直對齊多行間的標記,由於這會成爲維護的負擔(適用於:、#、=等)

Yes:
     foo = 1000  # comment
     long_name = 2  # comment that should not be aligned
 
     dictionary = {
         "foo": 1,
         "long_name": 2,
         }
No:
     foo       = 1000  # comment
     long_name = 2     # comment that should not be aligned
 
     dictionary = {
         "foo"      : 1,
         "long_name": 2,
         }

Shebang

大部分py文件沒必要以#!做爲文件的開始,根據 PEP-394 ,程序的main文件應該以 #!/usr/bin/python2或者 #!/usr/bin/python3開始。

(在計算機科學中,Shebang (也稱爲Hashbang)是一個由井號和歎號構成的字符串行(#!),其出如今文本文件的第一行的前兩個字符,在文件中存在Shebang的狀況下,類Unix操做系統的程序載入器會分析Shebang後的內容,將這些內容做爲解釋器指令,並調用該指令,並將載有Shebang的文件路徑做爲該解釋器的參數,例如,以指令#!/bin/sh開頭的文件在執行時會實際調用/bin/sh程序)

#!先用於幫助內核找到Python解釋器,可是在導入模塊時,將會被忽略,所以只有被直接執行的文件中才有必要加入#!。

註釋

確保對模塊、函數、方法和行內註釋使用正確的風格

文檔字符串

Python有一種獨一無二的的註釋方式:使用文檔字符串。文檔字符串是包、模塊、類或函數裏的第一個語句,這些字符串能夠經過對象的__doc__成員被自動提取,而且被pydoc所用(你能夠在你的模塊上運行pydoc試一把,看看它長什麼樣)。咱們對文檔字符串的慣例是使用三重雙引號」「」( PEP-257 )。一個文檔字符串應該這樣組織:首先是一行以句號,問號或驚歎號結尾的概述(或者該文檔字符串單純只有一行),接着是一個空行,接着是文檔字符串剩下的部分,它應該與文檔字符串的第一行的第一個引號對齊。下面有更多文檔字符串的格式化規範:

模塊

每一個文件應該包含一個許可樣板,根據項目使用的許可(例如Apache 2.0、BSD、LGPL、GPL),選擇合適的樣板。

函數和方法

下文所指的函數,包括函數、方法、以及生成器,一個函數必需要有文檔字符串,除非它知足如下條件:

  1. 外部不可見
  2. 很是短小
  3. 簡單明瞭

文檔字符串應該包含函數作什麼,以及輸入和輸出的詳細描述,一般不該該描述」怎麼作」,除非是一些複雜的算法。文檔字符串應該提供足夠的信息,當別人編寫代碼調用該函數時,他不須要看一行代碼,只要看文檔字符串就能夠了,對於複雜的代碼,在代碼旁邊加註釋會比使用文檔字符串更有意義。

關於函數的幾個方面應該在特定的小節中進行描述記錄,這幾個方面以下文所述:每節應該以一個標題行開始,標題行以冒號結尾,除標題行外,節的其餘內容應被縮進2個空格。

Args:

列出每一個參數的名字,並在名字後使用一個冒號和一個空格,分隔對該參數的描述。若是描述太長超過了單行80字符,使用2或者4個空格的懸掛縮進(與文件其餘部分保持一致)。描述應該包括所需的類型和含義,若是一個函數接受*foo(可變長度參數列表)或者**bar (任意關鍵字參數),應該詳細列出*foo和**bar。

Returns:(或者Yields生成器)

描述返回值的類型和語義,若是函數返回None,這一部分能夠省略。

Raises:

列出與接口有關的全部異常。

def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
    """Fetches rows from a Bigtable.
 
    Retrieves rows pertaining to the given keys from the Table instance
    represented by big_table.  Silly things may happen if
    other_silly_variable is not None.
 
    Args:
        big_table: An open Bigtable Table instance.
        keys: A sequence of strings representing the key of each table row
            to fetch.
        other_silly_variable: Another optional variable, that has a much
            longer name than the other args, and which does nothing.
 
    Returns:
        A dict mapping keys to the corresponding table row data
        fetched. Each row is represented as a tuple of strings. For
        example:
 
        {'Serak': ('Rigel VII', 'Preparer'),
         'Zim': ('Irk', 'Invader'),
         'Lrrr': ('Omicron Persei 8', 'Emperor')}
 
        If a key from the keys argument is missing from the dictionary,
        then that row was not found in the table.
 
    Raises:
        IOError: An error occurred accessing the bigtable.Table object.
    """
    pass

類應該在其定義下有一個用於描述該類的文檔字符串,若是你的類有公共屬性(Attributes),那麼文檔中應該有一個屬性(Attributes)段,而且應該遵照和函數參數相同的格式。

class SampleClass(object):
    """Summary of class here.
 
    Longer class information....
    Longer class information....
 
    Attributes:
        likes_spam: A boolean indicating if we like SPAM or not.
        eggs: An integer count of the eggs we have laid.
    """
 
    def __init__(self, likes_spam=False):
        """Inits SampleClass with blah."""
        self.likes_spam = likes_spam
        self.eggs = 0
 
    def public_method(self):
        """Performs operation blah."""
塊註釋和行註釋

最須要寫註釋的是代碼中那些技巧性的部分,若是你在下次 代碼審查 的時候必須解釋一下,那麼你應該如今就給它寫註釋。對於複雜的操做,應該在其操做開始前寫上若干行註釋。對於不是一目瞭然的代碼,應在其行尾添加註釋。

# We use a weighted dictionary search to find out where i is in
# the array.  We extrapolate position based on the largest num
# in the array and the array size and then do binary search to
# get the exact number.
 
if i & (i-1) == 0:        # true if i is a power of 2

爲了提升可讀性,註釋應該至少離開代碼2個空格。另外一方面,毫不要描述代碼,假設閱讀代碼的人比你更懂Python,他只是不知道你的代碼要作什麼。

# BAD COMMENT: Now go through the b array and make sure whenever i occurs
# the next element is i+1

若是一個類不繼承自其它類,就顯式的從object繼承,嵌套類也同樣。

Yes: class SampleClass(object):
         pass
 
 
     class OuterClass(object):
 
         class InnerClass(object):
             pass
 
 
     class ChildClass(ParentClass):
         """Explicitly inherits from another class already."""
 
No: class SampleClass:
        pass
 
 
    class OuterClass:
 
        class InnerClass:
            pass

繼承自 object 是爲了使屬性(properties)正常工做,而且這樣能夠保護你的代碼,使其不受 PEP-3000的一個特殊的潛在不兼容性影響,這樣作也定義了一些特殊的方法,這些方法實現了對象的默認語義,包括 __new__, __init__, __delattr__, __getattribute__, __setattr__, __hash__, __repr__, and __str__ 。

字符串

即便參數都是字符串,使用%操做符或者格式化方法格式化字符串,不過也不能一律而論,你須要在+和%之間好好斷定。

Yes: x = a + b
     x = '%s, %s!' % (imperative, expletive)
     x = '{}, {}!'.format(imperative, expletive)
     x = 'name: %s; score: %d' % (name, n)
     x = 'name: {}; score: {}'.format(name, n)
No: x = '%s%s' % (a, b)  # use + in this case
    x = '{}{}'.format(a, b)  # use + in this case
    x = imperative + ', ' + expletive + '!'
    x = 'name: ' + name + '; score: ' + str(n)

避免在循環中用+和+=操做符來累加字符串,因爲字符串是不可變的,這樣作會建立沒必要要的臨時對象,而且致使二次方而不是線性的運行時間,做爲替代方案,你能夠將每一個子串加入列表,而後在循環結束後用 .join 鏈接列表(也能夠將每一個子串寫入一個 cStringIO.StringIO 緩存中)。

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

在同一個文件中,保持使用字符串引號的一致性,使用單引號’或者雙引號」之一用以引用字符串,並在同一文件中沿用。在字符串內可使用另一種引號,以免在字符串中使用。pylint已經加入了這一檢查。

Yes:
     Python('Why are you hiding your eyes?')
     Gollum("I'm scared of lint errors.")
     Narrator('"Good!" thought a happy Python reviewer.')
No:
     Python("Why are you hiding your eyes?")
     Gollum('The lint. It burns. It burns us.')
     Gollum("Always the great lint. Watching. Watching.")

爲多行字符串使用三重雙引號」「」而非三重單引號’‘’,當且僅當項目中使用單引號’來引用字符串時,纔可能會使用三重’‘’爲非文檔字符串的多行字符串來標識引用。文檔字符串必須使用三重雙引號」「」,不過要注意,一般用隱式行鏈接更清晰,由於多行字符串與程序其餘部分的縮進方式不一致。

Yes:
    print ("This is much nicer.\n"
           "Do it this way.\n")
No:
      print """This is pretty ugly.
  Don't do this.
  """

文件和 sockets

在文件和sockets結束時,顯式的關閉它。

除文件外,sockets或其餘相似文件的對象在沒有必要的狀況下打開,會有許多反作用,例如:

  1. 它們可能會消耗有限的系統資源,如文件描述符,若是這些資源在使用後沒有及時歸還系統,那麼用於處理這些對象的代碼會將資源消耗殆盡。
  2. 持有文件將會阻止對於文件的其餘諸如移動、刪除之類的操做。
  3. 僅僅是從邏輯上關閉文件和sockets,那麼它們仍然可能會被其共享的程序在無心中進行讀或者寫操做,只有當它們真正被關閉後,對於它們嘗試進行讀或者寫操做將會跑出異常,並使得問題快速顯現出來。

並且幻想當文件對象析構時,文件和sockets會自動關閉,試圖將文件對象的生命週期和文件的狀態綁定在一塊兒的想法,都是不現實的,由於有以下緣由:

  1. 沒有任何方法能夠確保運行環境會真正的執行文件的析構,不一樣的Python實現採用不一樣的內存管理技術,好比延時垃圾處理機制,延時垃圾處理機制可能會致使對象生命週期被任意無限制的延長。
  2. 對於文件意外的引用,會致使對於文件的持有時間超出預期(好比對於異常的跟蹤,包含有全局變量等)。

推薦使用 「with」語句 以管理文件:

with open("hello.txt") as hello_file:
    for line in hello_file:
        print line

對於不支持使用」with」語句的相似文件的對象,使用 contextlib.closing():

import contextlib
 
with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
    for line in front_page:
        print line

Legacy AppEngine 中Python 2.5的代碼如使用」with」語句,須要添加 「from __future__ import with_statement」。

TODO註釋

爲臨時代碼使用TODO註釋,它是一種短時間解決方案,不算完美,但夠好了。

TODO註釋應該在全部開頭處包含」TODO」字符串,緊跟着是用括號括起來的你的名字,email地址或其它標識符,而後是一個可選的冒號,接着必須有一行註釋,解釋要作什麼。主要目的是爲了有一個統一的TODO格式,這樣添加註釋的人就能夠搜索到(並能夠按需提供更多細節)。寫了TODO註釋並不保證寫的人會親自解決問題,當你寫了一個TODO,請註上你的名字。

# TODO(kl@gmail.com): Use a "*" here for string repetition.
# TODO(Zeke) Change this to use relations.

若是你的TODO是」未來作某事」的形式,那麼請確保你包含了一個指定的日期(「2016年11月解決」)或者一個特定的事件(「等到全部的客戶均可以處理XML請求就移除這些代碼」)。

導入格式

每一個導入應該獨佔一行

Yes: import os
     import sys
No:  import os, sys

導入總應該放在文件頂部,位於模塊註釋和文檔字符串以後,模塊全局變量和常量以前,導入應該按照從最通用到最不通用的順序分組:

  1. 標準庫導入
  2. 第三方庫導入
  3. 應用程序指定導入

每種分組中,應該根據每一個模塊的完整包路徑按字典序排序,忽略大小寫。

import foo
from foo import bar
from foo.bar import baz
from foo.bar import Quux
from Foob import ar

語句

一般每一個語句應該獨佔一行

不過,若是測試結果與測試語句在一行放得下,你也能夠將它們放在同一行,若是是if語句,只有在沒有else時才能這樣作。毫不要對 try/except 這樣作,由於try和except不能放在同一行。

Yes:
 
  if foo: bar(foo)
No:
 
  if foo: bar(foo)
  else:   baz(foo)
 
  try:               bar(foo)
  except ValueError: baz(foo)
 
  try:
      bar(foo)
  except ValueError: baz(foo)

訪問控制

 

在Python中,對於瑣碎又不過重要的訪問函數,你應該直接使用公有變量來取代它們,這樣能夠避免額外的函數調用開銷,當添加更多功能時,你能夠用屬性(property)來保持語法的一致性。

(重視封裝的面向對象程序員看到這個可能會很反感,由於他們一直被教育:全部成員變量都必須是私有的! 其實,那真的是有點麻煩啊,試着去接受Pythonic哲學吧)

另外一方面,若是訪問更復雜,或者變量的訪問開銷很顯著,那麼你應該使用像 get_foo() 和 set_foo()這樣的函數調用。若是以前的代碼行爲容許經過屬性(property)訪問 ,那麼就不要將新的訪問函數與屬性綁定,這樣,任何試圖經過老方法訪問變量的代碼就無法運行,使用者也就會意識到複雜性發生了變化。

命名

module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_VAR_NAME, instance_var_name, function_parameter_name, local_var_name.

應該避免的名稱
  1. 單字符名稱,除了計數器和迭代器。
  2. 包/模塊名中的連字符(-)
  3. 雙下劃線開頭並結尾的名稱(Python保留,例如__init__)
命名約定
  1. 所謂」內部(Internal)」表示僅模塊內可用,或者在類內是保護或私有的。
  2. 用單下劃線(_)開頭表示模塊變量或函數是protected的(使用import * from時不會包含)。
  3. 用雙下劃線(__)開頭的實例變量或方法表示類內私有。
  4. 將相關的類和頂級函數放在同一個模塊裏,不像Java,不必限制一個類一個模塊。
  5. 對類名使用大寫字母開頭的單詞(如CapWords,即Pascal風格),可是模塊名應該用小寫加下劃線的方式(如lower_with_under.py),儘管已經有不少現存的模塊使用相似於CapWords.py這樣的命名,但如今已經不鼓勵這樣作,由於若是模塊名碰巧和類名一致,這會讓人困擾。
Python之父Guido推薦的規範

Type

Public

Internal

Modules

lower_with_under

_lower_with_under

Packages

lower_with_under

 

Classes

CapWords

_CapWords

Exceptions

CapWords

 

Functions

lower_with_under()

_lower_with_under()

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

lower_with_under()

_lower_with_under() (protected) or __lower_with_under() (private)

Function/Method Parameters

lower_with_under

 

Local Variables

lower_with_under

 

 

Main

即便是一個打算被用做腳本的文件,也應該是可導入的,而且簡單的導入不該該致使這個腳本的主功能(main functionality)被執行,這是一種反作用,主功能應該放在一個main()函數中。

在Python中,pydoc以及單元測試要求模塊必須是可導入的,你的代碼應該在執行主程序前老是檢查if __name__ == '__main__' ,這樣當模塊被導入時主程序就不會被執行。

def main():
      ...
 
if __name__ == '__main__':
    main()

全部的頂級代碼在模塊導入時都會被執行,要當心不要去調用函數,建立對象,或者執行那些不該該在使用pydoc時執行的操做。

其餘

請務必保持代碼的一致性

若是你正在編輯代碼,花幾分鐘看一下週邊代碼,而後決定風格。若是它們在全部的算術操做符兩邊都使用空格,那麼你也應該這樣作。若是它們的註釋都用標記包圍起來,那麼你的註釋也要這樣。

制定風格指南的目的在於讓代碼有規可循,這樣人們就能夠專一於」你在說什麼」,而不是」你在怎麼說」。咱們在這裏給出的是全局的規範,可是本地的規範一樣重要。若是你加到一個文件裏的代碼和原有代碼截然不同,它會讓讀者不知所措,避免這種狀況。

 

 

 

 

Ti 

Tip 

相關文章
相關標籤/搜索