Google Python風(fēng)格指南

  • Python是一種對(duì)代碼風(fēng)格很重視的語(yǔ)言,從縮進(jìn)就能看出這一點(diǎn),Python強(qiáng)調(diào)易于理解。最近在負(fù)責(zé)代碼重構(gòu)的工作,為了統(tǒng)一大家的代碼風(fēng)格,制訂規(guī)范,學(xué)習(xí)了一下網(wǎng)上這份Google的Python風(fēng)格指南。

背景

Python 是 Google主要的腳本語(yǔ)言。這本風(fēng)格指南主要包含的是針對(duì)python的編程準(zhǔn)則。
為幫助讀者能夠?qū)⒋a準(zhǔn)確格式化,我們提供了針對(duì) Vim的配置文件 。對(duì)于Emacs用戶,保持默認(rèn)設(shè)置即可。

Python語(yǔ)言規(guī)范

pylint

Tip
對(duì)你的代碼運(yùn)行pylint

定義:
pylint是一個(gè)在Python源代碼中查找bug的工具. 對(duì)于C和C++這樣的不那么動(dòng)態(tài)的(譯者注: 原文是less dynamic)語(yǔ)言, 這些bug通常由編譯器來(lái)捕獲. 由于Python的動(dòng)態(tài)特性, 有些警告可能不對(duì). 不過(guò)偽告警應(yīng)該很少.
優(yōu)點(diǎn):
可以捕獲容易忽視的錯(cuò)誤, 例如輸入錯(cuò)誤, 使用未賦值的變量等.
缺點(diǎn):
pylint不完美. 要利用其優(yōu)勢(shì), 我們有時(shí)侯需要: a) 圍繞著它來(lái)寫代碼 b) 抑制其告警 c) 改進(jìn)它, 或者d) 忽略它.
結(jié)論:
確保對(duì)你的代碼運(yùn)行pylint.抑制不準(zhǔn)確的警告,以便能夠?qū)⑵渌姹┞冻鰜?lái)。
你可以通過(guò)設(shè)置一個(gè)行注釋來(lái)抑制告警. 例如:

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

pylint警告是以一個(gè)數(shù)字編號(hào)(如 C0112 )和一個(gè)符號(hào)名(如 empty-docstring )來(lái)標(biāo)識(shí)的. 在編寫新代碼或更新已有代碼時(shí)對(duì)告警進(jìn)行醫(yī)治, 推薦使用符號(hào)名來(lái)標(biāo)識(shí).

如果警告的符號(hào)名不夠見(jiàn)名知意,那么請(qǐng)對(duì)其增加一個(gè)詳細(xì)解釋。

采用這種抑制方式的好處是我們可以輕松查找抑制并回顧它們.

你可以使用命令 pylint --list-msgs 來(lái)獲取pylint告警列表. 你可以使用命令 pylint --help-msg=C6409 , 以獲取關(guān)于特定消息的更多信息.

相比較于之前使用的 pylint: disable-msg , 本文推薦使用 pylint: disable .

要抑制”參數(shù)未使用”告警, 你可以用””作為參數(shù)標(biāo)識(shí)符, 或者在參數(shù)名前加”unused”. 遇到不能改變參數(shù)名的情況, 你可以通過(guò)在函數(shù)開(kāi)頭”提到”它們來(lái)消除告警. 例如:

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

導(dǎo)入

Tip
僅對(duì)包和模塊使用導(dǎo)入

定義:
模塊間共享代碼的重用機(jī)制.
優(yōu)點(diǎn):
命名空間管理約定十分簡(jiǎn)單. 每個(gè)標(biāo)識(shí)符的源都用一種一致的方式指示. x.Obj表示Obj對(duì)象定義在模塊x中.
缺點(diǎn):
模塊名仍可能沖突. 有些模塊名太長(zhǎng), 不太方便.
結(jié)論:
使用 import x 來(lái)導(dǎo)入包和模塊.

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

使用 from x import y as z, 如果兩個(gè)要導(dǎo)入的模塊都叫做z或者y太長(zhǎng)了.

例如, 模塊 sound.effects.echo 可以用如下方式導(dǎo)入:

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

導(dǎo)入時(shí)不要使用相對(duì)名稱. 即使模塊在同一個(gè)包中, 也要使用完整包名. 這能幫助你避免無(wú)意間導(dǎo)入一個(gè)包兩次.

Tip
使用模塊的全路徑名來(lái)導(dǎo)入每個(gè)模塊

優(yōu)點(diǎn):
避免模塊名沖突. 查找包更容易.
缺點(diǎn):
部署代碼變難, 因?yàn)槟惚仨殢?fù)制包層次.
結(jié)論:
所有的新代碼都應(yīng)該用完整包名來(lái)導(dǎo)入每個(gè)模塊.

應(yīng)該像下面這樣導(dǎo)入:

# Reference in code with complete name.
import sound.effects.echo

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

異常

Tip
允許使用異常, 但必須小心

定義:
異常是一種跳出代碼塊的正常控制流來(lái)處理錯(cuò)誤或者其它異常條件的方式.
優(yōu)點(diǎn):
正常操作代碼的控制流不會(huì)和錯(cuò)誤處理代碼混在一起. 當(dāng)某種條件發(fā)生時(shí), 它也允許控制流跳過(guò)多個(gè)框架. 例如, 一步跳出N個(gè)嵌套的函數(shù), 而不必繼續(xù)執(zhí)行錯(cuò)誤的代碼.
缺點(diǎn):
可能會(huì)導(dǎo)致讓人困惑的控制流. 調(diào)用庫(kù)時(shí)容易錯(cuò)過(guò)錯(cuò)誤情況.
結(jié)論:
異常必須遵守特定條件:

  1. 像這樣觸發(fā)異常: raise MyException("Error message") 或者 raise MyException . 不要使用兩個(gè)參數(shù)的形式( raise MyException, "Error message" )或者過(guò)時(shí)的字符串異常( raise "Error message" ).

  2. 模塊或包應(yīng)該定義自己的特定域的異常基類, 這個(gè)基類應(yīng)該從內(nèi)建的Exception類繼承. 模塊的異常基類應(yīng)該叫做”Error”.

    class Error(Exception):
        pass
  1. 永遠(yuǎn)不要使用 except: 語(yǔ)句來(lái)捕獲所有異常, 也不要捕獲 Exception 或者 StandardError , 除非你打算重新觸發(fā)該異常, 或者你已經(jīng)在當(dāng)前線程的最外層(記得還是要打印一條錯(cuò)誤消息). 在異常這方面, Python非常寬容, except: 真的會(huì)捕獲包括Python語(yǔ)法錯(cuò)誤在內(nèi)的任何錯(cuò)誤. 使用 except: 很容易隱藏真正的bug.

  2. 盡量減少try/except塊中的代碼量. try塊的體積越大, 期望之外的異常就越容易被觸發(fā). 這種情況下, try/except塊將隱藏真正的錯(cuò)誤.

  3. 使用finally子句來(lái)執(zhí)行那些無(wú)論try塊中有沒(méi)有異常都應(yīng)該被執(zhí)行的代碼. 這對(duì)于清理資源常常很有用, 例如關(guān)閉文件.
    當(dāng)捕獲異常時(shí), 使用 as 而不要用逗號(hào). 例如

try:
    raise Error
except Error as error:
    pass

全局變量

Tip
避免全局變量

定義:
定義在模塊級(jí)的變量.
優(yōu)點(diǎn):
偶爾有用.
缺點(diǎn):
導(dǎo)入時(shí)可能改變模塊行為, 因?yàn)閷?dǎo)入模塊時(shí)會(huì)對(duì)模塊級(jí)變量賦值.
結(jié)論:
避免使用全局變量, 用類變量來(lái)代替. 但也有一些例外:

  1. 腳本的默認(rèn)選項(xiàng).
  2. 模塊級(jí)常量. 例如: PI = 3.14159. 常量應(yīng)該全大寫, 用下劃線連接.
  3. 有時(shí)候用全局變量來(lái)緩存值或者作為函數(shù)返回值很有用.
  4. 如果需要, 全局變量應(yīng)該僅在模塊內(nèi)部可用, 并通過(guò)模塊級(jí)的公共函數(shù)來(lái)訪問(wèn).

嵌套 局部 內(nèi)部類或函數(shù)

Tip
鼓勵(lì)使用嵌套/本地/內(nèi)部類或函數(shù)

定義:
類可以定義在方法, 函數(shù)或者類中. 函數(shù)可以定義在方法或函數(shù)中. 封閉區(qū)間中定義的變量對(duì)嵌套函數(shù)是只讀的.
優(yōu)點(diǎn):
允許定義僅用于有效范圍的工具類和函數(shù).
缺點(diǎn):
嵌套類或局部類的實(shí)例不能序列化(pickled).
結(jié)論:
推薦使用.

列表推導(dǎo) List Comprehensions

Tip
可以在簡(jiǎn)單情況下使用

定義:
列表推導(dǎo)(list comprehensions)與生成器表達(dá)式(generator expression)提供了一種簡(jiǎn)潔高效的方式來(lái)創(chuàng)建列表和迭代器, 而不必借助map(), filter(), 或者lambda.
優(yōu)點(diǎn):
簡(jiǎn)單的列表推導(dǎo)可以比其它的列表創(chuàng)建方法更加清晰簡(jiǎn)單. 生成器表達(dá)式可以十分高效, 因?yàn)樗鼈儽苊饬藙?chuàng)建整個(gè)列表.
缺點(diǎn):
復(fù)雜的列表推導(dǎo)或者生成器表達(dá)式可能難以閱讀.
結(jié)論:
適用于簡(jiǎn)單情況. 每個(gè)部分應(yīng)該單獨(dú)置于一行: 映射表達(dá)式, for語(yǔ)句, 過(guò)濾器表達(dá)式. 禁止多重for語(yǔ)句或過(guò)濾器表達(dá)式. 復(fù)雜情況下還是使用循環(huán).

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)

默認(rèn)迭代器和操作符

Tip
如果類型支持, 就使用默認(rèn)迭代器和操作符. 比如列表, 字典及文件等.

定義:
容器類型, 像字典和列表, 定義了默認(rèn)的迭代器和關(guān)系測(cè)試操作符(in和not in)
優(yōu)點(diǎn):
默認(rèn)操作符和迭代器簡(jiǎn)單高效, 它們直接表達(dá)了操作, 沒(méi)有額外的方法調(diào)用. 使用默認(rèn)操作符的函數(shù)是通用的. 它可以用于支持該操作的任何類型.
缺點(diǎn):
你沒(méi)法通過(guò)閱讀方法名來(lái)區(qū)分對(duì)象的類型(例如, has_key()意味著字典). 不過(guò)這也是優(yōu)點(diǎn).
結(jié)論:
如果類型支持, 就使用默認(rèn)迭代器和操作符, 例如列表, 字典和文件. 內(nèi)建類型也定義了迭代器方法. 優(yōu)先考慮這些方法, 而不是那些返回列表的方法. 當(dāng)然,這樣遍歷容器時(shí),你將不能修改容器.

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

生成器

Tip
按需使用生成器.

定義:
所謂生成器函數(shù), 就是每當(dāng)它執(zhí)行一次生成(yield)語(yǔ)句, 它就返回一個(gè)迭代器, 這個(gè)迭代器生成一個(gè)值. 生成值后, 生成器函數(shù)的運(yùn)行狀態(tài)將被掛起, 直到下一次生成.
優(yōu)點(diǎn):
簡(jiǎn)化代碼, 因?yàn)槊看握{(diào)用時(shí), 局部變量和控制流的狀態(tài)都會(huì)被保存. 比起一次創(chuàng)建一系列值的函數(shù), 生成器使用的內(nèi)存更少.
缺點(diǎn):
沒(méi)有.
結(jié)論:
鼓勵(lì)使用. 注意在生成器函數(shù)的文檔字符串中使用”Yields:”而不是”Returns:”.
(譯者注: 參看 注釋 )

Lambda函數(shù)

Tip
適用于單行函數(shù)

定義:
與語(yǔ)句相反, lambda在一個(gè)表達(dá)式中定義匿名函數(shù). 常用于為 map()filter() 之類的高階函數(shù)定義回調(diào)函數(shù)或者操作符.
優(yōu)點(diǎn):
方便.
缺點(diǎn):
比本地函數(shù)更難閱讀和調(diào)試. 沒(méi)有函數(shù)名意味著堆棧跟蹤更難理解. 由于lambda函數(shù)通常只包含一個(gè)表達(dá)式, 因此其表達(dá)能力有限.
結(jié)論:
適用于單行函數(shù). 如果代碼超過(guò)60-80個(gè)字符, 最好還是定義成常規(guī)(嵌套)函數(shù).

對(duì)于常見(jiàn)的操作符,例如乘法操作符,使用 operator 模塊中的函數(shù)以代替lambda函數(shù). 例如, 推薦使用 operator.mul , 而不是 lambda x, y: x * y .

條件表達(dá)式

Tip
適用于單行函數(shù)

定義:
條件表達(dá)式是對(duì)于if語(yǔ)句的一種更為簡(jiǎn)短的句法規(guī)則. 例如: x = 1 if cond else 2 .
優(yōu)點(diǎn):
比if語(yǔ)句更加簡(jiǎn)短和方便.
缺點(diǎn):
比if語(yǔ)句難于閱讀. 如果表達(dá)式很長(zhǎng), 難于定位條件.
結(jié)論:
適用于單行函數(shù). 在其他情況下,推薦使用完整的if語(yǔ)句.

默認(rèn)參數(shù)值

Tip
適用于大部分情況.

定義:
你可以在函數(shù)參數(shù)列表的最后指定變量的值, 例如, def foo(a, b = 0): . 如果調(diào)用foo時(shí)只帶一個(gè)參數(shù), 則b被設(shè)為0. 如果帶兩個(gè)參數(shù), 則b的值等于第二個(gè)參數(shù).
優(yōu)點(diǎn):
你經(jīng)常會(huì)碰到一些使用大量默認(rèn)值的函數(shù), 但偶爾(比較少見(jiàn))你想要覆蓋這些默認(rèn)值. 默認(rèn)參數(shù)值提供了一種簡(jiǎn)單的方法來(lái)完成這件事, 你不需要為這些罕見(jiàn)的例外定義大量函數(shù). 同時(shí), Python也不支持重載方法和函數(shù), 默認(rèn)參數(shù)是一種”仿造”重載行為的簡(jiǎn)單方式.
缺點(diǎn):
默認(rèn)參數(shù)只在模塊加載時(shí)求值一次. 如果參數(shù)是列表或字典之類的可變類型, 這可能會(huì)導(dǎo)致問(wèn)題. 如果函數(shù)修改了對(duì)象(例如向列表追加項(xiàng)), 默認(rèn)值就被修改了.
結(jié)論:
鼓勵(lì)使用, 不過(guò)有如下注意事項(xiàng):

不要在函數(shù)或方法定義中使用可變對(duì)象作為默認(rèn)值.

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

Tip
訪問(wèn)和設(shè)置數(shù)據(jù)成員時(shí), 你通常會(huì)使用簡(jiǎn)單, 輕量級(jí)的訪問(wèn)和設(shè)置函數(shù). 建議用屬性(properties)來(lái)代替它們.

定義:
一種用于包裝方法調(diào)用的方式. 當(dāng)運(yùn)算量不大, 它是獲取和設(shè)置屬性(attribute)的標(biāo)準(zhǔn)方式.
優(yōu)點(diǎn):
通過(guò)消除簡(jiǎn)單的屬性(attribute)訪問(wèn)時(shí)顯式的get和set方法調(diào)用, 可讀性提高了. 允許懶惰的計(jì)算. 用Pythonic的方式來(lái)維護(hù)類的接口. 就性能而言, 當(dāng)直接訪問(wèn)變量是合理的, 添加訪問(wèn)方法就顯得瑣碎而無(wú)意義. 使用屬性(properties)可以繞過(guò)這個(gè)問(wèn)題. 將來(lái)也可以在不破壞接口的情況下將訪問(wèn)方法加上.
缺點(diǎn):
屬性(properties)是在get和set方法聲明后指定, 這需要使用者在接下來(lái)的代碼中注意: set和get是用于屬性(properties)的(除了用 @property 裝飾器創(chuàng)建的只讀屬性). 必須繼承自object類. 可能隱藏比如操作符重載之類的副作用. 繼承時(shí)可能會(huì)讓人困惑.
結(jié)論:
你通常習(xí)慣于使用訪問(wèn)或設(shè)置方法來(lái)訪問(wèn)或設(shè)置數(shù)據(jù), 它們簡(jiǎn)單而輕量. 不過(guò)我們建議你在新的代碼中使用屬性. 只讀屬性應(yīng)該用 @property 裝飾器 來(lái)創(chuàng)建.

如果子類沒(méi)有覆蓋屬性, 那么屬性的繼承可能看上去不明顯. 因此使用者必須確保訪問(wèn)方法間接被調(diào)用, 以保證子類中的重載方法被屬性調(diào)用(使用模板方法設(shè)計(jì)模式).

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

(譯者注: 老實(shí)說(shuō), 我覺(jué)得這段示例代碼很不恰當(dāng), 有必要這么蛋疼嗎?)

True or False的求值

Tip
盡可能使用隱式false

定義:
Python在布爾上下文中會(huì)將某些值求值為false. 按簡(jiǎn)單的直覺(jué)來(lái)講, 就是所有的”空”值都被認(rèn)為是false. 因此0, None, [], {}, “” 都被認(rèn)為是false.
優(yōu)點(diǎn):
使用Python布爾值的條件語(yǔ)句更易讀也更不易犯錯(cuò). 大部分情況下, 也更快.
缺點(diǎn):
對(duì)C/C++開(kāi)發(fā)人員來(lái)說(shuō), 可能看起來(lái)有點(diǎn)怪.
結(jié)論:
盡可能使用隱式的false, 例如: 使用 if foo: 而不是 if foo != []: . 不過(guò)還是有一些注意事項(xiàng)需要你銘記在心:

  1. 永遠(yuǎn)不要用==或者!=來(lái)比較單件, 比如None. 使用is或者is not.

  2. 注意: 當(dāng)你寫下 if x: 時(shí), 你其實(shí)表示的是 if x is not None . 例如: 當(dāng)你要測(cè)試一個(gè)默認(rèn)值是None的變量或參數(shù)是否被設(shè)為其它值. 這個(gè)值在布爾語(yǔ)義下可能是false!

  3. 永遠(yuǎn)不要用==將一個(gè)布爾量與false相比較. 使用 if not x: 代替. 如果你需要區(qū)分false和None, 你應(yīng)該用像 if not x and x is not None: 這樣的語(yǔ)句.

  4. 對(duì)于序列(字符串, 列表, 元組), 要注意空序列是false. 因此 if not seq: 或者 if seq:if len(seq):if not len(seq): 要更好.

  5. 處理整數(shù)時(shí), 使用隱式false可能會(huì)得不償失(即不小心將None當(dāng)做0來(lái)處理). 你可以將一個(gè)已知是整型(且不是len()的返回結(jié)果)的值與0比較.

Yes: if not users:
         print 'no users'

     if foo == 0:
         self.handle_zero()

     if i % 10 == 0:
         self.handle_multiple_of_ten()
         
No:  if len(users) == 0:
         print 'no users'

     if foo is not None and not foo:
         self.handle_zero()

     if not i % 10:
         self.handle_multiple_of_ten()
  1. 注意‘0’(字符串)會(huì)被當(dāng)做true.

過(guò)時(shí)的語(yǔ)言特性

Tip
盡可能使用字符串方法取代字符串模塊. 使用函數(shù)調(diào)用語(yǔ)法取代apply(). 使用列表推導(dǎo), for循環(huán)取代filter(),map()以及reduce().

定義:
當(dāng)前版本的Python提供了大家通常更喜歡的替代品.
結(jié)論:
我們不使用不支持這些特性的Python版本, 所以沒(méi)理由不用新的方式.

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

Tip

推薦使用

定義:
嵌套的Python函數(shù)可以引用外層函數(shù)中定義的變量, 但是不能夠?qū)λ鼈冑x值. 變量綁定的解析是使用詞法作用域, 也就是基于靜態(tài)的程序文本. 對(duì)一個(gè)塊中的某個(gè)名稱的任何賦值都會(huì)導(dǎo)致Python將對(duì)該名稱的全部引用當(dāng)做局部變量, 甚至是賦值前的處理. 如果碰到global聲明, 該名稱就會(huì)被視作全局變量.

一個(gè)使用這個(gè)特性的例子:

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

    return adder

(譯者注: 這個(gè)例子有點(diǎn)詭異, 你應(yīng)該這樣使用這個(gè)函數(shù): sum = get_adder(summand1)(summand2) )
優(yōu)點(diǎn):
通常可以帶來(lái)更加清晰, 優(yōu)雅的代碼. 尤其會(huì)讓有經(jīng)驗(yàn)的Lisp和Scheme(還有Haskell, ML等)程序員感到欣慰.
缺點(diǎn):
可能導(dǎo)致讓人迷惑的bug. 例如下面這個(gè)依據(jù) 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]) 會(huì)打印 1 2 3 3 , 不是 1 2 3 4 .

(譯者注: x是一個(gè)列表, for循環(huán)其實(shí)是將x中的值依次賦給i.這樣對(duì)i的賦值就隱式的發(fā)生了, 整個(gè)foo函數(shù)體中的i都會(huì)被當(dāng)做局部變量, 包括bar()中的那個(gè). 這一點(diǎn)與C++之類的靜態(tài)語(yǔ)言還是有很大差別的.)
結(jié)論:
鼓勵(lì)使用.

函數(shù)與方法裝飾器

Tip
如果好處很顯然, 就明智而謹(jǐn)慎的使用裝飾器

定義:
用于函數(shù)及方法的裝飾器 (也就是@標(biāo)記). 最常見(jiàn)的裝飾器是@classmethod 和@staticmethod, 用于將常規(guī)函數(shù)轉(zhuǎn)換成類方法或靜態(tài)方法. 不過(guò), 裝飾器語(yǔ)法也允許用戶自定義裝飾器. 特別地, 對(duì)于某個(gè)函數(shù) my_decorator , 下面的兩段代碼是等效的:

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

優(yōu)點(diǎn):
優(yōu)雅的在函數(shù)上指定一些轉(zhuǎn)換. 該轉(zhuǎn)換可能減少一些重復(fù)代碼, 保持已有函數(shù)不變(enforce invariants), 等.
缺點(diǎn):
裝飾器可以在函數(shù)的參數(shù)或返回值上執(zhí)行任何操作, 這可能導(dǎo)致讓人驚異的隱藏行為. 而且, 裝飾器在導(dǎo)入時(shí)執(zhí)行. 從裝飾器代碼的失敗中恢復(fù)更加不可能.
結(jié)論:
如果好處很顯然, 就明智而謹(jǐn)慎的使用裝飾器. 裝飾器應(yīng)該遵守和函數(shù)一樣的導(dǎo)入和命名規(guī)則. 裝飾器的python文檔應(yīng)該清晰的說(shuō)明該函數(shù)是一個(gè)裝飾器. 請(qǐng)為裝飾器編寫單元測(cè)試.

避免裝飾器自身對(duì)外界的依賴(即不要依賴于文件, socket, 數(shù)據(jù)庫(kù)連接等), 因?yàn)檠b飾器運(yùn)行時(shí)這些資源可能不可用(由 pydoc 或其它工具導(dǎo)入). 應(yīng)該保證一個(gè)用有效參數(shù)調(diào)用的裝飾器在所有情況下都是成功的.

裝飾器是一種特殊形式的”頂級(jí)代碼”. 參考后面關(guān)于 Main 的話題.

線程

Tip
不要依賴內(nèi)建類型的原子性.

雖然Python的內(nèi)建類型例如字典看上去擁有原子操作, 但是在某些情形下它們?nèi)匀徊皇窃拥?即: 如果__hash____eq__被實(shí)現(xiàn)為Python方法)且它們的原子性是靠不住的. 你也不能指望原子變量賦值(因?yàn)檫@個(gè)反過(guò)來(lái)依賴字典).

優(yōu)先使用Queue模塊的 Queue 數(shù)據(jù)類型作為線程間的數(shù)據(jù)通信方式. 另外, 使用threading模塊及其鎖原語(yǔ)(locking primitives). 了解條件變量的合適使用方式, 這樣你就可以使用 threading.Condition 來(lái)取代低級(jí)別的鎖了.

威力過(guò)大的特性

Tip
避免使用這些特性

定義:
Python是一種異常靈活的語(yǔ)言, 它為你提供了很多花哨的特性, 諸如元類(metaclasses), 字節(jié)碼訪問(wèn), 任意編譯(on-the-fly compilation), 動(dòng)態(tài)繼承, 對(duì)象父類重定義(object reparenting), 導(dǎo)入黑客(import hacks), 反射, 系統(tǒng)內(nèi)修改(modification of system internals), 等等.
優(yōu)點(diǎn):
強(qiáng)大的語(yǔ)言特性, 能讓你的代碼更緊湊.
缺點(diǎn):
使用這些很”酷”的特性十分誘人, 但不是絕對(duì)必要. 使用奇技淫巧的代碼將更加難以閱讀和調(diào)試. 開(kāi)始可能還好(對(duì)原作者而言), 但當(dāng)你回顧代碼, 它們可能會(huì)比那些稍長(zhǎng)一點(diǎn)但是很直接的代碼更加難以理解.
結(jié)論:
在你的代碼中避免這些特性.

Python風(fēng)格規(guī)范

分號(hào)

Tip
不要在行尾加分號(hào), 也不要用分號(hào)將兩條命令放在同一行.

行長(zhǎng)度

Tip
每行不超過(guò)80個(gè)字符

例外:

  1. 長(zhǎng)的導(dǎo)入模塊語(yǔ)句
  2. 注釋里的URL

不要使用反斜杠連接行.

Python會(huì)將 圓括號(hào), 中括號(hào)和花括號(hào)中的行隱式的連接起來(lái) , 你可以利用這個(gè)特點(diǎn). 如果需要, 你可以在表達(dá)式外圍增加一對(duì)額外的圓括號(hào).

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

如果一個(gè)文本字符串在一行放不下, 可以使用圓括號(hào)來(lái)實(shí)現(xiàn)隱式行連接:

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

在注釋中,如果必要,將長(zhǎng)的URL放在一行上。

Yes:  # See details at
      # http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html
No:  # See details at
     # http://www.example.com/us/developer/documentation/api/content/\
     # v2.0/csv_file_name_extension_full_specification.html

注意上面例子中的元素縮進(jìn); 你可以在本文的 縮進(jìn) 部分找到解釋.

括號(hào)

Tip
寧缺毋濫的使用括號(hào)

除非是用于實(shí)現(xiàn)行連接, 否則不要在返回語(yǔ)句或條件語(yǔ)句中使用括號(hào). 不過(guò)在元組兩邊使用括號(hào)是可以的.

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)

縮進(jìn)

Tip
用4個(gè)空格來(lái)縮進(jìn)代碼

絕對(duì)不要用tab, 也不要tab和空格混用. 對(duì)于行連接的情況, 你應(yīng)該要么垂直對(duì)齊換行的元素(見(jiàn) 行長(zhǎng)度 部分的示例), 或者使用4空格的懸掛式縮進(jìn)(這時(shí)第一行不應(yīng)該有參數(shù)):

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,
              ...
      }

空行

Tip
頂級(jí)定義之間空兩行, 方法定義之間空一行

頂級(jí)定義之間空兩行, 比如函數(shù)或者類定義. 方法定義, 類定義與第一個(gè)方法之間, 都應(yīng)該空一行. 函數(shù)或方法中, 某些地方要是你覺(jué)得合適, 就空一行.

空格

Tip
按照標(biāo)準(zhǔn)的排版規(guī)范來(lái)使用標(biāo)點(diǎn)兩邊的空格

括號(hào)內(nèi)不要有空格.

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

不要在逗號(hào), 分號(hào), 冒號(hào)前面加空格, 但應(yīng)該在它們后面加(除了在行尾).

Yes: if x == 4:
         print x, y
     x, y = y, x
No:  if x == 4 :
         print x , y
     x , y = y , x

參數(shù)列表, 索引或切片的左括號(hào)前不應(yīng)加空格.

Yes: spam(1)
no: spam (1)
Yes: dict['key'] = list[index]
No:  dict ['key'] = list [index]

在二元操作符兩邊都加上一個(gè)空格, 比如賦值(=), 比較(==, <, >, !=, <>, <=, >=, in, not in, is, is not), 布爾(and, or, not). 至于算術(shù)操作符兩邊的空格該如何使用, 需要你自己好好判斷. 不過(guò)兩側(cè)務(wù)必要保持一致.

Yes: x == 1
No:  x<1

當(dāng)’=’用于指示關(guān)鍵字參數(shù)或默認(rèn)參數(shù)值時(shí), 不要在其兩側(cè)使用空格.

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)

不要用空格來(lái)垂直對(duì)齊多行間的標(biāo)記, 因?yàn)檫@會(huì)成為維護(hù)的負(fù)擔(dān)(適用于:, #, =等):

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,
         }

Python 解析器

Tip
大部分.py文件不必以#!作為文件的開(kāi)始. 根據(jù) PEP-394 , 程序的main文件應(yīng)該以#!/usr/bin/python2或者 #!/usr/bin/python3開(kāi)始.

(譯者注: 在計(jì)算機(jī)科學(xué)中, Shebang (也稱為Hashbang)是一個(gè)由井號(hào)和嘆號(hào)構(gòu)成的字符串行(#!), 其出現(xiàn)在文本文件的第一行的前兩個(gè)字符. 在文件中存在Shebang的情況下, 類Unix操作系統(tǒng)的程序載入器會(huì)分析Shebang后的內(nèi)容, 將這些內(nèi)容作為解釋器指令, 并調(diào)用該指令, 并將載有Shebang的文件路徑作為該解釋器的參數(shù). 例如, 以指令#!/bin/sh開(kāi)頭的文件在執(zhí)行時(shí)會(huì)實(shí)際調(diào)用/bin/sh程序.)
#!先用于幫助內(nèi)核找到Python解釋器, 但是在導(dǎo)入模塊時(shí), 將會(huì)被忽略. 因此只有被直接執(zhí)行的文件中才有必要加入#!.

注釋

Tip
確保對(duì)模塊, 函數(shù), 方法和行內(nèi)注釋使用正確的風(fēng)格 文檔字符串

Python有一種獨(dú)一無(wú)二的的注釋方式: 使用文檔字符串. 文檔字符串是包, 模塊, 類或函數(shù)里的第一個(gè)語(yǔ)句. 這些字符串可以通過(guò)對(duì)象的doc成員被自動(dòng)提取, 并且被pydoc所用. (你可以在你的模塊上運(yùn)行pydoc試一把, 看看它長(zhǎng)什么樣). 我們對(duì)文檔字符串的慣例是使用三重雙引號(hào)”“”( PEP-257 ). 一個(gè)文檔字符串應(yīng)該這樣組織: 首先是一行以句號(hào), 問(wèn)號(hào)或驚嘆號(hào)結(jié)尾的概述(或者該文檔字符串單純只有一行). 接著是一個(gè)空行. 接著是文檔字符串剩下的部分, 它應(yīng)該與文檔字符串的第一行的第一個(gè)引號(hào)對(duì)齊. 下面有更多文檔字符串的格式化規(guī)范.
模塊

每個(gè)文件應(yīng)該包含一個(gè)許可樣板. 根據(jù)項(xiàng)目使用的許可(例如, Apache 2.0, BSD, LGPL, GPL), 選擇合適的樣板.
函數(shù)和方法

下文所指的函數(shù),包括函數(shù), 方法, 以及生成器.

一個(gè)函數(shù)必須要有文檔字符串, 除非它滿足以下條件:

  1. 外部不可見(jiàn)
  2. 非常短小
  3. 簡(jiǎn)單明了

文檔字符串應(yīng)該包含函數(shù)做什么, 以及輸入和輸出的詳細(xì)描述. 通常, 不應(yīng)該描述”怎么做”, 除非是一些復(fù)雜的算法. 文檔字符串應(yīng)該提供足夠的信息, 當(dāng)別人編寫代碼調(diào)用該函數(shù)時(shí), 他不需要看一行代碼, 只要看文檔字符串就可以了. 對(duì)于復(fù)雜的代碼, 在代碼旁邊加注釋會(huì)比使用文檔字符串更有意義.

關(guān)于函數(shù)的幾個(gè)方面應(yīng)該在特定的小節(jié)中進(jìn)行描述記錄, 這幾個(gè)方面如下文所述. 每節(jié)應(yīng)該以一個(gè)標(biāo)題行開(kāi)始. 標(biāo)題行以冒號(hào)結(jié)尾. 除標(biāo)題行外, 節(jié)的其他內(nèi)容應(yīng)被縮進(jìn)2個(gè)空格.

Args:
列出每個(gè)參數(shù)的名字, 并在名字后使用一個(gè)冒號(hào)和一個(gè)空格, 分隔對(duì)該參數(shù)的描述.如果描述太長(zhǎng)超過(guò)了單行80字符,使用2或者4個(gè)空格的懸掛縮進(jìn)(與文件其他部分保持一致). 描述應(yīng)該包括所需的類型和含義. 如果一個(gè)函數(shù)接受foo(可變長(zhǎng)度參數(shù)列表)或者bar (任意關(guān)鍵字參數(shù)), 應(yīng)該詳細(xì)列出foo和**bar.

Returns: (或者 Yields: 用于生成器)
描述返回值的類型和語(yǔ)義. 如果函數(shù)返回None, 這一部分可以省略.

Raises:
列出與接口有關(guān)的所有異常.

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

類應(yīng)該在其定義下有一個(gè)用于描述該類的文檔字符串. 如果你的類有公共屬性(Attributes), 那么文檔中應(yīng)該有一個(gè)屬性(Attributes)段. 并且應(yīng)該遵守和函數(shù)參數(shù)相同的格式.

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

塊注釋和行注釋

最需要寫注釋的是代碼中那些技巧性的部分. 如果你在下次 代碼審查 的時(shí)候必須解釋一下, 那么你應(yīng)該現(xiàn)在就給它寫注釋. 對(duì)于復(fù)雜的操作, 應(yīng)該在其操作開(kāi)始前寫上若干行注釋. 對(duì)于不是一目了然的代碼, 應(yīng)在其行尾添加注釋.

# 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 iff i is a power of 2

為了提高可讀性, 注釋應(yīng)該至少離開(kāi)代碼2個(gè)空格.

另一方面, 絕不要描述代碼. 假設(shè)閱讀代碼的人比你更懂Python, 他只是不知道你的代碼要做什么.

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

Tip
如果一個(gè)類不繼承自其它類, 就顯式的從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)正常工作, 并且這樣可以保護(hù)你的代碼, 使其不受Python 3000的一個(gè)特殊的潛在不兼容性影響. 這樣做也定義了一些特殊的方法, 這些方法實(shí)現(xiàn)了對(duì)象的默認(rèn)語(yǔ)義, 包括 __new__, __init__, __delattr__, __getattribute__, __setattr__, __hash__, __repr__, and __str__ .

字符串

Tip
即使參數(shù)都是字符串, 使用%操作符或者格式化方法格式化字符串. 不過(guò)也不能一概而論, 你需要在+和%之間好好判定.

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)

避免在循環(huán)中用+和+=操作符來(lái)累加字符串. 由于字符串是不可變的, 這樣做會(huì)創(chuàng)建不必要的臨時(shí)對(duì)象, 并且導(dǎo)致二次方而不是線性的運(yùn)行時(shí)間. 作為替代方案, 你可以將每個(gè)子串加入列表, 然后在循環(huán)結(jié)束后用 .join 連接列表. (也可以將每個(gè)子串寫入一個(gè) 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>'

在同一個(gè)文件中, 保持使用字符串引號(hào)的一致性. 使用單引號(hào)’或者雙引號(hào)”之一用以引用字符串, 并在同一文件中沿用. 在字符串內(nèi)可以使用另外一種引號(hào), 以避免在字符串中使用. GPyLint已經(jīng)加入了這一檢查.

(譯者注:GPyLint疑為筆誤, 應(yīng)為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.")

為多行字符串使用三重雙引號(hào)”“”而非三重單引號(hào)’‘’. 當(dāng)且僅當(dāng)項(xiàng)目中使用單引號(hào)’來(lái)引用字符串時(shí), 才可能會(huì)使用三重’‘’為非文檔字符串的多行字符串來(lái)標(biāo)識(shí)引用. 文檔字符串必須使用三重雙引號(hào)”“”. 不過(guò)要注意, 通常用隱式行連接更清晰, 因?yàn)槎嘈凶址c程序其他部分的縮進(jìn)方式不一致.

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

文件和sockets

Tip
在文件和sockets結(jié)束時(shí), 顯式的關(guān)閉它.

除文件外, sockets或其他類似文件的對(duì)象在沒(méi)有必要的情況下打開(kāi), 會(huì)有許多副作用, 例如:

  1. 它們可能會(huì)消耗有限的系統(tǒng)資源,如文件描述符.如果這些資源在使用后沒(méi)有及時(shí)歸還系統(tǒng),那么用于處理這些對(duì)象的代碼會(huì)將資源消耗殆盡.
  2. 持有文件將會(huì)阻止對(duì)于文件的其他諸如移動(dòng)、刪除之類的操作.
  3. 僅僅是從邏輯上關(guān)閉文件和sockets,那么它們?nèi)匀豢赡軙?huì)被其共享的程序在無(wú)意中進(jìn)行讀或者寫操作.只有當(dāng)它們真正被關(guān)閉后,對(duì)于它們嘗試進(jìn)行讀或者寫操作將會(huì)跑出異常,并使得問(wèn)題快速顯現(xiàn)出來(lái).

而且,幻想當(dāng)文件對(duì)象析構(gòu)時(shí),文件和sockets會(huì)自動(dòng)關(guān)閉,試圖將文件對(duì)象的生命周期和文件的狀態(tài)綁定在一起的想法,都是不現(xiàn)實(shí)的. 因?yàn)橛腥缦略?

  1. 沒(méi)有任何方法可以確保運(yùn)行環(huán)境會(huì)真正的執(zhí)行文件的析構(gòu).不同的Python實(shí)現(xiàn)采用不同的內(nèi)存管理技術(shù),比如延時(shí)垃圾處理機(jī)制. 延時(shí)垃圾處理機(jī)制可能會(huì)導(dǎo)致對(duì)象生命周期被任意無(wú)限制的延長(zhǎng).
  2. 對(duì)于文件意外的引用,會(huì)導(dǎo)致對(duì)于文件的持有時(shí)間超出預(yù)期(比如對(duì)于異常的跟蹤, 包含有全局變量等).

推薦使用“with”語(yǔ)句 以管理文件:

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

對(duì)于不支持使用”with”語(yǔ)句的類似文件的對(duì)象,使用 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”語(yǔ)句, 需要添加 “from __future__ import with_statement”.

TODO注釋

Tip
為臨時(shí)代碼使用TODO注釋, 它是一種短期解決方案. 不算完美, 但夠好了.

TODO注釋應(yīng)該在所有開(kāi)頭處包含”TODO”字符串, 緊跟著是用括號(hào)括起來(lái)的你的名字, email地址或其它標(biāo)識(shí)符. 然后是一個(gè)可選的冒號(hào). 接著必須有一行注釋, 解釋要做什么. 主要目的是為了有一個(gè)統(tǒng)一的TODO格式, 這樣添加注釋的人就可以搜索到(并可以按需提供更多細(xì)節(jié)). 寫了TODO注釋并不保證寫的人會(huì)親自解決問(wèn)題. 當(dāng)你寫了一個(gè)TODO, 請(qǐng)注上你的名字.

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

如果你的TODO是”將來(lái)做某事”的形式, 那么請(qǐng)確保你包含了一個(gè)指定的日期(“2009年11月解決”)或者一個(gè)特定的事件(“等到所有的客戶都可以處理XML請(qǐng)求就移除這些代碼”).

導(dǎo)入格式

Tip
每個(gè)導(dǎo)入應(yīng)該獨(dú)占一行

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

導(dǎo)入總應(yīng)該放在文件頂部, 位于模塊注釋和文檔字符串之后, 模塊全局變量和常量之前. 導(dǎo)入應(yīng)該按照從最通用到最不通用的順序分組:

  1. 標(biāo)準(zhǔn)庫(kù)導(dǎo)入
  2. 第三方庫(kù)導(dǎo)入
  3. 應(yīng)用程序指定導(dǎo)入

每種分組中, 應(yīng)該根據(jù)每個(gè)模塊的完整包路徑按字典序排序, 忽略大小寫.

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

語(yǔ)句

Tip
通常每個(gè)語(yǔ)句應(yīng)該獨(dú)占一行

不過(guò), 如果測(cè)試結(jié)果與測(cè)試語(yǔ)句在一行放得下, 你也可以將它們放在同一行. 如果是if語(yǔ)句, 只有在沒(méi)有else時(shí)才能這樣做. 特別地, 絕不要對(duì) try/except 這樣做, 因?yàn)閠ry和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)

訪問(wèn)控制

Tip
在Python中, 對(duì)于瑣碎又不太重要的訪問(wèn)函數(shù),你應(yīng)該直接使用公有變量來(lái)取代它們,這樣可以避免額外的函數(shù)調(diào)用開(kāi)銷.當(dāng)添加更多功能時(shí), 你可以用屬性(property)來(lái)保持語(yǔ)法的一致性.

(譯者注: 重視封裝的面向?qū)ο蟪绦騿T看到這個(gè)可能會(huì)很反感, 因?yàn)樗麄円恢北唤逃? 所有成員變量都必須是私有的! 其實(shí), 那真的是有點(diǎn)麻煩啊.試著去接受Pythonic哲學(xué)吧)

另一方面, 如果訪問(wèn)更復(fù)雜, 或者變量的訪問(wèn)開(kāi)銷很顯著, 那么你應(yīng)該使用像 get_foo()set_foo() 這樣的函數(shù)調(diào)用. 如果之前的代碼行為允許通過(guò)屬性(property)訪問(wèn) , 那么就不要將新的訪問(wèn)函數(shù)與屬性綁定. 這樣, 任何試圖通過(guò)老方法訪問(wèn)變量的代碼就沒(méi)法運(yùn)行, 使用者也就會(huì)意識(shí)到復(fù)雜性發(fā)生了變化.

命名

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

應(yīng)該避免的名稱

  1. 單字符名稱, 除了計(jì)數(shù)器和迭代器.
  2. 包/模塊名中的連字符(-)
  3. 雙下劃線開(kāi)頭并結(jié)尾的名稱(Python保留, 例如init)

命名約定

  1. 所謂”內(nèi)部(Internal)”表示僅模塊內(nèi)可用, 或者, 在類內(nèi)是保護(hù)或私有的.
  2. 用單下劃線(_)開(kāi)頭表示模塊變量或函數(shù)是protected的(使用import * from時(shí)不會(huì)包含).
  3. 用雙下劃線(__)開(kāi)頭的實(shí)例變量或方法表示類內(nèi)私有.
  4. 將相關(guān)的類和頂級(jí)函數(shù)放在同一個(gè)模塊里. 不像Java, 沒(méi)必要限制一個(gè)類一個(gè)模塊.
  5. 對(duì)類名使用大寫字母開(kāi)頭的單詞(如CapWords,即Pascal風(fēng)格),但是模塊名應(yīng)該用小寫加下劃線的方式(如lower_with_under.py). 盡管已經(jīng)有很多現(xiàn)存的模塊使用類似于CapWords.py這樣的命名,但現(xiàn)在已經(jīng)不鼓勵(lì)這樣做,因?yàn)槿绻K名碰巧和類一致, 這會(huì)讓人困擾.
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

Python之父Guido推薦的規(guī)范

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

Tip
即使是一個(gè)打算被用作腳本的文件,也應(yīng)該是可導(dǎo)入的.并且簡(jiǎn)單的導(dǎo)入不應(yīng)該導(dǎo)致這個(gè)腳本的主功能(mainfunctionality)被執(zhí)行, 這是一種副作用. 主功能應(yīng)該放在一個(gè)main()函數(shù)中.

在Python中, pydoc以及單元測(cè)試要求模塊必須是可導(dǎo)入的. 你的代碼應(yīng)該在執(zhí)行主程序前總是檢查 if __name__ == '__main__' , 這樣當(dāng)模塊被導(dǎo)入時(shí)主程序就不會(huì)被執(zhí)行.

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

所有的頂級(jí)代碼在模塊導(dǎo)入時(shí)都會(huì)被執(zhí)行. 要小心不要去調(diào)用函數(shù), 創(chuàng)建對(duì)象或者執(zhí)行那些不應(yīng)該在使用pydoc時(shí)執(zhí)行的操作.

臨別贈(zèng)言

請(qǐng)務(wù)必保持代碼的一致性

如果你正在編輯代碼, 花幾分鐘看一下周邊代碼,然后決定風(fēng)格.如果它們?cè)谒械乃阈g(shù)操作符兩邊都使用空格,那么你也應(yīng)該這樣做. 如果它們的注釋都用標(biāo)記包圍起來(lái), 那么你的注釋也要這樣.

制定風(fēng)格指南的目的在于讓代碼有規(guī)可循,這樣人們就可以專注于”你在說(shuō)什么”,而不是”你在怎么說(shuō)”.我們?cè)谶@里給出的是全局的規(guī)范, 但是本地的規(guī)范同樣重要.如果你加到一個(gè)文件里的代碼和原有代碼大相徑庭,它會(huì)讓讀者不知所措.避免這種情況.

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評(píng)論 3 422
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,947評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,201評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,960評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,350評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,549評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,914評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,089評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,340評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,753評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,007評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,834評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,106評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容