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

2018-02-24 15:12 更新

Lint

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)寫(xiě)代碼 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í)的. 在編寫(xiě)新代碼或更新已有代碼時(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)閉文件.

  4. 當(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)該全大寫(xiě), 用下劃線連接.
  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/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)你寫(xiě)下?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)為裝飾器編寫(xiě)單元測(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)匀徊皇窃拥?即: 如果hasheq被實(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é)論:

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

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)