原文:Google Python Style Guide
前言
這篇風格指南針對于python渔工,給出了一些建議磷账。涉及了兩個方面,一個是有關(guān)python語法的規(guī)范猎唁,比如異常怎么用骑篙、什么時候用列表生成器等等,這部分是關(guān)于python語言本身的使用指南余爆;另一個是關(guān)于python代碼的風格斋配,比如如何進行縮進、如何續(xù)行等等寄悯,這部分讓代碼看上去很緊湊萤衰。這兩個方面對于代碼的可讀性、代碼以后的可維護性都有很積極的意義猜旬,對于Code Review大有幫助脆栋。
這篇風格指南只是給出了google的一些規(guī)范和建議,有許多地方并不是強制要求這么做洒擦。但正如上所說椿争,為了代碼更加緊湊、美觀熟嫩,增強代碼可讀性和可維護性秦踪。我們應(yīng)當遵守這樣的指導。
文檔中提到了幾個工具pylint掸茅、yapf椅邓、pytype,可以查閱一下使用方式昧狮,可以讓代碼盡可能的滿足本文提到的各種規(guī)范建議景馁。
下面開始正文。
1 背景
python是谷歌使用的主要動態(tài)語言逗鸣。該風格指南指出了python編程中一些該做的和不該做的行為合住。為了幫助讀者正確地格式化代碼,谷歌創(chuàng)建了settings file for Vim慕购。對Emacs來說聊疲,默認配置沒有問題。許多團隊使用自動化格式工具yapf來避免代碼格式上的爭議沪悲。
2 python語言規(guī)則
2.1 Lint
使用pylint
檢查你的代碼获洲。
2.1.1 定義
pylint
是用來檢查python源代碼bug和格式問題的工具。對于不那么動態(tài)的語言(C或C++)殿如,這些bug通常由編譯器來捕獲贡珊。由于python的動態(tài)特性,許多警告可能不正確涉馁;然而门岔,這種假警告的情況很少出現(xiàn)。
2.1.2 優(yōu)點
可以捕獲容易忽視的錯誤烤送,比如拼寫錯誤寒随、使用未賦值的變量等等。
2.1.3 缺點
pylint
并不完美。想要充分利用它妻往,我們有時需要:
- 圍繞它來寫代碼
- 禁用它的警告
- 優(yōu)化它
2.1.4 建議
確保使用pylint
檢查你的代碼互艾。
禁用不恰當?shù)木妫悦夂雎云渌麊栴}讯泣。為了禁用警告纫普,你可以設(shè)置一行注釋:
dict = 'something awful' # Bad Idea... pylint: disable=redefined-builtin
pylint警告都是用符號名稱標識(empty-docstring
),谷歌特定的警告帶有g-
前綴好渠。
如果從符號名稱中看不出去除警告的理由昨稼,那就要做出解釋。
以這種方式禁用警告有一種優(yōu)點拳锚,就是我們可以輕易找到該警告并訪問它假栓。
你可以用一下方式獲取pylint
警告的列表:
pylint --list-msgs
想得到某一條特定消息(比如C6409)的詳細信息:
pylint --help-msg=C6409
優(yōu)先使用pylint:disable
,而不是棄用的舊格式pylint:disable-msg
晌畅。
對于未使用的參數(shù)的警告但指,我們可以通過在函數(shù)一開始刪除這些變量來抑制。一定要注釋一下為什么要刪除這些變量抗楔,“Unused”就足夠了棋凳,例如:
def viking_cafe_order(spam, beans, eggs=None):
del beans, eggs # Unused by vikings.
return spam + spam + spam
其他抑制這種警告的通用格式包括使用‘_
’作為未使用參數(shù)的標識符、在參數(shù)前面加‘unused
’前綴或者將它們賦值給‘_
’连躏。這些格式是允許的剩岳,但是已經(jīng)不再推薦使用。前兩種方式中斷通過變量名傳參的調(diào)用入热,最后一種方式并不強迫參數(shù)真的不能使用拍棕。
2.2 Imports(引用)
只能對包或者模塊使用import
,而不能對類或者函數(shù)使用勺良。
2.2.1 定義
模塊間共享代碼的復用機制绰播。
2.2.2 優(yōu)點
名稱空間的管理約定非常簡單,每一個標識符的源都以一致的方式給出尚困。x.Obj
表示Obj
對象定義在模塊x
中蠢箩。
2.2.3 缺點
模塊名字依然會發(fā)生沖突,許多模塊的名字很長事甜,不方便谬泌。
2.2.4 建議
- 使用
import x
來引用包和模塊 - 使用
from x import y
,其中x
是包前綴逻谦,y
是不帶前綴的模塊名 - 使用
from x import y as z
掌实,如果引入的兩個包都叫x
或者y
的名字很長 - 使用
import y as z
,只有當z
是標準縮寫時邦马,例如numpy
縮寫為nm
舉個例子贱鼻,模塊sound.effects.echo
可以用下面的方式引用:
from sound.effects import echo
...
echo.EchoFilter(input, output, delay=0.7, atten=4)
引用包時不要使用相對名稱宴卖,即使模塊在同一個包中,也要使用完整的包名稱忱嘹。這可以避免兩次引入同一個包嘱腥。
注意引用typing模塊時有明確的豁免。
2.3 Packages(包)
使用模塊的完整路徑名位置導入每個模塊拘悦。
2.3.1 優(yōu)點
避免模塊名中的沖突。更容易找到模塊橱脸。
2.3.2 缺點
部署代碼更加困難础米,因為你必須復制包的層次結(jié)構(gòu)。
2.3.3 建議
所有新代碼都應(yīng)該按其完整的包名導入每個模塊添诉。
# Reference in code with complete name.
import absl.flags
# Reference in code with just module name (preferred).
from absl import flags
2.4 Exceptions(異常)
異常必須要小心使用屁桑。
2.4.1 定義
異常是一種工具,它可以中斷一點代碼的正忱父埃控制流來處理錯誤或其他異常情況蘑斧。
2.4.2 優(yōu)點
正常運行的代碼控制流不會被錯誤處理代碼干擾。當特定條件發(fā)生時须眷,它也允許控制流跳過多層框架竖瘾。例如,在N層嵌套函數(shù)中從某一步返回花颗,而不必執(zhí)行錯誤代碼捕传。
2.4.3 缺點
可能會讓控制流顯得比較混亂,調(diào)用庫時容易錯過出錯的情況扩劝。
2.4.4 建議
異常必須遵循某些條件:
- 以下面的方式拋出異常:
raise MyError('Error message')
或者raise MyError()
庸论,不要使用兩個參數(shù)的形式raise MyError, 'Error message'
。 - 盡量使用內(nèi)置的異常類棒呛。例如聂示,如果在期望正數(shù)的地方傳入了負數(shù),就拋出
ValueError
異常簇秒。不要使用assert
語句來驗證公共API的參數(shù)值鱼喉,assert
用來確保內(nèi)部的正確性,而不是用來強制正確使用什么宰睡,也不是用來指示意外事件的發(fā)生蒲凶。如果后一種情況期望拋出異常,那就使用raise語句拆内。例如旋圆,
Yes:
def ConnectToNextPort(self, minimum):
"""Connects to the next available port. Returns the new minimum port."""
if minimum <= 1024:
raise ValueError('Minimum port must be greater than 1024.')
port = self._FindNextOpenPort(minimum)
if not port:
raise ConnectionError('Could not connect to service on %d or higher.' % (minimum,))
assert port >= minimum, 'Unexpected port %d when minimum was %d.' % (port, minimum)
return port
No:
def ConnectToNextPort(self, minimum):
"""Connects to the next available port. Returns the new minimum port."""
assert minimum > 1024, 'Minimum port must be greater than 1024.'
port = self._FindNextOpenPort(minimum)
assert port is not None
return port
- 類庫或包可能會定義自己的異常類。這種情況下麸恍,自定義的異常類必須繼承已有的異常類灵巧。異常名稱應(yīng)當以
Error
結(jié)尾搀矫,而且不應(yīng)太繞口(foo.FooError
).注:原文是stutter
。 - 永遠不要使用捕獲所有異常的
except:
語句刻肄,也不要捕獲Exception
和StandardError
瓤球,除非你打算重新出發(fā)該異常或者在當前線程的最外層(此時記得打印一條錯誤信息)敏弃。Python在這方面非常寬容卦羡,except:
會捕獲拼寫錯誤、sys.exit()調(diào)用麦到、 Ctrl+C中斷绿饵、單元測試失敗以及所有你不想捕獲的其它異常。 -
try
/except
塊中的代碼要盡量少瓶颠,try
塊代碼越多拟赊,就越容易在你不希望拋出異常的地方拋出異常。這種情況下粹淋,try
/except
塊隱藏了真正的錯誤吸祟。 - 使用
finally
子句執(zhí)行無論try
塊是否拋出異常都會執(zhí)行的代碼,這對收尾工作很有用桃移,比如關(guān)閉文件屋匕。 - 捕獲異常時,使用
as
谴轮,不要用逗號炒瘟。例如,
try:
raise Error
except Error as error:
pass
2.5 Global variables(全局變量)
避免使用全局變量第步。
2.5.1 定義
在模塊級別聲明的變量疮装,或者作為類屬性的變量。
2.5.2 優(yōu)點
偶爾很有用粘都。
2.5.3 缺點
有可能在引入包的時候改變模塊的行為廓推。因為模塊在第一次被引入的時候,對全局變量的賦值就完成了翩隧。
2.5.4 建議
避免使用全局變量樊展。
模塊級別的常量雖然本質(zhì)上也是變量,但它們是被允許并鼓勵使用的堆生。例如专缠,
MAX_HOLY_HANDGRENADE_COUNT = 3
,常量必須用大寫字母命名淑仆,可以帶有下劃線涝婉。參考下面的命名章節(jié)。
如果需要使用全局變量蔗怠,那它們應(yīng)該在模塊級別聲明墩弯,并且通過變量名頭部添加_
來設(shè)置為僅模塊內(nèi)部可見吩跋。外部的訪問必須通過模塊級別的共有函數(shù)來實現(xiàn),參考下面的命名章節(jié)渔工。
2.6 Nested/Local/Inner Classes and Functions(嵌套/局部/內(nèi)部類和函數(shù))
嵌套局部函數(shù)和類在閉包時很有用锌钮。內(nèi)部類很有用。
2.6.1 定義
一個類可以在一個方法引矩、函數(shù)或類中定義梁丘。函數(shù)可以定義在方法或函數(shù)中。嵌套函數(shù)對封閉作用域中定義的變量擁有只讀權(quán)限旺韭。
2.6.2 優(yōu)點
在很有限的作用域內(nèi)使用的通用類和函數(shù)允許被定義兰吟。
2.6.3 缺點
嵌套或局部類的實例不能被直接測試。嵌套使得外層函數(shù)更長茂翔,可讀性更差。
2.6.4 建議
考慮下述限制履腋,使用它們還不錯:除非閉包使用珊燎,其他情況下避免使用;不要為了不讓模塊調(diào)用者使用而嵌套函數(shù)遵湖,而是在模塊級將它的名字加上前綴_
悔政,,以便在測試時仍然可以訪問延旧。
2.7 Comprehensions & Generator Expressions(推導式 & 生成器表達式)
在簡單的情況下可以使用
2.7.1 定義
在不求助于傳統(tǒng)的循環(huán)谋国、map()
、filter()
迁沫、lambda
的情況下芦瘾,列表、字典和集合推導式以及生成器表達式提供了一種簡單高效的方式來生成容器類型和迭代器集畅。
2.7.2 優(yōu)點
簡單的推導式比字典近弟、列表或集合生成方式更加簡潔。生成器表達式可以非常高效挺智,因為它避免了生成整個列表祷愉。
2.7.3 缺點
復雜的推導式和生成器表達式會難以閱讀。
2.7.4 建議
簡單的情況下可以使用赦颇。每一部分必須適應(yīng)一行的長度:mapping表達式二鳄、for
子句、filter表達式媒怯。多層for
子句或filter表達式是不允許的订讼。當情況更復雜的時候使用循環(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)
2.8 Default Iterators and Operators(默認的迭代器和運算符)
如果類型支持的話沪摄,使用默認的迭代器和運算符躯嫉,像列表纱烘、字典和文件等。
2.8.1 定義
容器類型祈餐,比如字典和列表擂啥,定義了默認的迭代器和成員測試運算符(“in”和“not in”)。
2.8.2 優(yōu)點
默認的迭代器和運算符簡單高效帆阳。它們直接表達操作哺壶,沒有額外的方法調(diào)用。使用默認運算符的函數(shù)是通用的蜒谤。它可以和支持該運算的任何類型一起使用山宾。
2.8.3 缺點
通過方法名無法分辨對象類型(比如has_key()表示一個字典)。這其實也是一種優(yōu)勢鳍徽。
2.8.4 建議
如果類型支持的話资锰,使用默認的迭代器和運算符,像列表阶祭、字典和文件等绷杜。內(nèi)置的類型也定義了迭代器方法。相比返回列表的方法濒募,優(yōu)先使用這些方法鞭盟,除非你在迭代的時候不允許改變?nèi)萜鳌?/p>
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(): ...
2.9 Generators(生成器)
根據(jù)需要使用生成器。
2.9.1 定義
生成器函數(shù)返回一個迭代器瑰剃,這個迭代器每次執(zhí)行yield語句時會產(chǎn)生一個值齿诉。生成一個值后,生成器函數(shù)的運行狀態(tài)就處于暫停狀態(tài)晌姚,直到需要下一個值粤剧。
2.9.2 優(yōu)點
更簡潔的代碼。因為每次調(diào)用都保留了局部變量和控制流的狀態(tài)舀凛。生成器比一次運行就生成整個列表的函數(shù)要使用更少的內(nèi)存俊扳。
2.9.3 缺點
沒有。
2.9.4 建議
生成器函數(shù)的docstring中使用“Yields:”而不是“Returns:”猛遍。
2.10 Lambda Functions(Lambda函數(shù))
單行使用沒問題馋记。
2.10.1 定義
Lambdas與聲明相反,它在表達式中定義匿名函數(shù)懊烤。它通常被用來給更高層的函數(shù)定義回調(diào)或運算符梯醒,比如map()
和filter()
。
2.10.2 優(yōu)點
方便腌紧。
2.10.3 缺點
相比局部函數(shù)茸习,它更難閱讀和調(diào)試。匿名意味著堆棧跟蹤更難理解壁肋。表達能力有限号胚,因為函數(shù)可能只包含一個表達式籽慢。
2.10.4 建議
單行使用沒問題。如果Lambda表達式內(nèi)部的代碼多于60-80個字符猫胁,那么把它定義為普通(嵌套)函數(shù)可能更好箱亿。
對于常規(guī)的運算比如乘法,使用operator
模塊中的函數(shù)而不要使用Lambda函數(shù)弃秆。例如届惋,優(yōu)先使用operator.mul
,而不是lambda x, y: x * y
菠赚。
2.11 Conditional Expressions(條件表達式)
單行使用沒問題脑豹。
2.11.1 定義
條件表達式(有時也叫三元運算符)是一種給if語句提供更簡短語法的機制。例如衡查,x = 1 if cond else 2
瘩欺。
2.11.2 優(yōu)點
比if語句更短更方便。
2.11.3 缺點
比if語句更難閱讀拌牲。如果表達式很長的話击碗,條件很難定位。
2.11.4 建議
單行使用沒問題们拙。其他情況下優(yōu)先使用完整的if語句。
2.12 Default Argument Values(默認參數(shù)值)
多數(shù)情況下沒有問題阁吝。
2.12.1 定義
你可以在函數(shù)的參數(shù)列表末尾給變量指定值砚婆。例如,def foo(a, b=0):
突勇。如果foo
只用一個參數(shù)來調(diào)用装盯,那么b設(shè)置為0。如果函數(shù)用兩個參數(shù)的形式調(diào)用甲馋,那么b就是第二個參數(shù)的值埂奈。
2.12.2 優(yōu)點
經(jīng)常會遇到這種情況,你有一個函數(shù)使用許多默認值定躏,但你很少去覆蓋這些默認值账磺。默認參數(shù)值為此提供了一種簡單的方式,避免因少數(shù)例外情況定義多個函數(shù)痊远。而且垮抗,Python不支持重載方法或函數(shù),默認參數(shù)是“偽裝”重載的一個簡單方式碧聪。
2.12.3 缺點
默認參數(shù)只在模塊載入的時候求值一次冒版,如果參數(shù)是可變對象(例如列表或字典),就會引發(fā)問題逞姿。如果函數(shù)改變了對象(例如在列表后追加項)辞嗡,默認值就會被改變捆等。
2.12.4 建議
考慮一下建議可以使用:
在函數(shù)或方法中不要使用可變對象作為默認值。
Yes: def foo(a, b=None):
if b is None:
b = []
Yes: def foo(a, b: Optional[Sequence] = 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...
2.13 Properties(屬性)
在通常你會使用簡單輕量的訪問或設(shè)置方法來訪問或設(shè)置數(shù)據(jù)的地方換做用屬性來實現(xiàn)续室。
2.13.1 定義
當計算是輕量級的時候栋烤,把獲取或設(shè)置屬性的方法調(diào)用包裝為標準屬性訪問的一種方法。
2.13.2 優(yōu)點
通過顯式去除對簡單屬性訪問的獲取和設(shè)置方法猎贴,提高了代碼可讀性班缎。允許計算延遲∷剩考慮了以python的方式來維護類的接口达址。在性能方面,當直接訪問變量合情合理時趁耗,用方法去訪問屬性就變得沒有意義沉唠。這樣也使得以后可以在不破壞接口前提下添加訪問方法。
2.13.3 缺點
在python2中必須要繼承object
對象苛败。也會像操作符重載一樣隱藏副作用满葛。可能讓子類感到困惑罢屈。
2.13.4 建議
在通常你會使用簡單輕量的訪問或設(shè)置方法來訪問或設(shè)置數(shù)據(jù)的地方換做用屬性來實現(xiàn)嘀韧。屬性應(yīng)當用@property
修飾符來創(chuàng)建。如果屬性本身沒有被重寫缠捌,那么帶有屬性的繼承可能不明顯锄贷。因此必須確保訪問方法被間接調(diào)用來保證子類中重寫的方法被屬性調(diào)用。(使用模板方法DP)
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
@property
def area(self):
"""Gets or sets the area of the square."""
return self._get_area()
@area.setter
def area(self, area):
return self._set_area(area)
def _get_area(self):
"""Indirect accessor to calculate the 'area' property."""
return self.side ** 2
def _set_area(self, area):
"""Indirect setter to set the 'area' property."""
self.side = math.sqrt(area)
@property
def perimeter(self):
return self.side * 4
2.14 True/False evaluations(True/False值)
盡可能使用隱式false值曼月。
2.14.1 定義
在布爾上下文中谊却,python將特定的值解析為False
⊙魄郏快速“經(jīng)驗法則”就是所有“空”值都被認為是False
炎辨,因此,0, None, [], {}, ''
在布爾環(huán)境下都被解析為False
聪姿。
2.14.2 優(yōu)點
使用python布爾值作為條件更容易閱讀碴萧,更不易出錯。多數(shù)情況下末购,運行也更快勿决。
2.14.3 缺點
對C/C++開發(fā)人員來說這有點奇怪。
2.14.4 建議
盡可能使用隱式false值招盲。例如低缩,if foo:
而不是if foo != []:
。然而還有幾個警告需要記住:
- 永遠不要使用
==
或!=
來比較單件(比如None
)咆繁,使用is
或is not
讳推。 - 當你真正想表示的是
if x is not None:
時,謹慎處理if x:
玩般。例如银觅,當測試一個默認值為None
的變量或參數(shù)是不是設(shè)為了其他值,這個其他值在布爾上下文中可能是false坏为。 - 永遠不要用
==
將一個布爾變量和False
相比究驴,使用if not x:
。如果你想?yún)^(qū)分False
和None
匀伏,使用鏈接表達式洒忧。例如,if not x and x is not None:
够颠。 - 對于序列(字符串熙侍、列表、元組)履磨,空序列就是假蛉抓。因此
if seq:
和if not seq:
分別要比if len(seq):
和if not len(seq):
。 - 當處理整數(shù)時剃诅,隱式的假值帶來的風險可能比好處更大巷送。比如不小心把
None
當作0處理。你可能將一個確認的整數(shù)值和這個“0”比較矛辕。
Yes: if not users:
print('no users')
if foo == 0:
self.handle_zero()
if i % 10 == 0:
self.handle_multiple_of_ten()
def f(x=None):
if x is None:
x = []
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()
def f(x=None):
x = x or []
- 注意
'0'
(字符串0)在布爾值中是True惩系。
2.15 Deprecated Language Features(棄用的語言特性)
盡可能使用字符串方法代替字符串模塊。使用函數(shù)調(diào)用語法代替apply
如筛。當函數(shù)參數(shù)是內(nèi)聯(lián)lambda表達式時,無論如何使用列表推導式或for
循環(huán)代替filter
和map
抒抬。使用for
循環(huán)代替reduce
杨刨。
2.15.1 定義
python的當前版本提供了人們更愿意使用的可替代結(jié)構(gòu)。
2.15.2 建議
我們不使用不支持這些特性的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)
2.16 Lexical Scoping(靜態(tài)作用域)
放心使用。
2.16.1 定義
嵌套的python函數(shù)可以引用封閉函數(shù)中定義的變量惠勒,但是不能給它們賦值赚抡。變量綁定是用靜態(tài)作用域解析的,也是說基于靜態(tài)程序文本纠屋。代碼塊中任何對變量的賦值都會讓python將該變量的所有引用當作局部變量涂臣,即使在賦值之前使用。如果有全局聲明,那么變量就被當作全局變量赁遗。該特性使用的一個例子:
def get_adder(summand1):
"""Returns a function that adds numbers to a given number."""
def adder(summand2):
return summand1 + summand2
return adder
2.16.2 優(yōu)點
通常會產(chǎn)生更加簡潔優(yōu)雅的代碼署辉。尤其會讓有經(jīng)驗的Lisp和Scheme程序員感到欣慰。
2.16.3 缺點
可能會導致令人困惑的bug岩四。比如這個例子PEP-0227哭尝。
i = 4
def foo(x):
def bar():
print(i, end='')
# ...
# A bunch of code here
# ...
for i in x: # Ah, i *is* local to foo, so this is what bar sees
print(i, end='')
bar()
因此,foo([1, 2, 3])
會打印1 2 3 3
而不是1 2 3 4
剖煌。
2.16.4 建議
推薦使用材鹦。
2.17 Function and Method Decorators(函數(shù)和方法裝飾器)
當有明顯得好處時使用。避免使用@staticmethod
耕姊,限制@classmethod
的使用桶唐。
2.17.1 定義
Decorators for Functions and Methods(也成為@
標記符)。一個常用的裝飾器是@property
箩做,用來把普通的方法轉(zhuǎn)換為動態(tài)計算的屬性莽红。而且,裝飾器語言也允許用戶自定義邦邦。特別地安吁,對某個函數(shù)my_decorator
,下面的代碼:
class C(object):
@my_decorator
def method(self):
# method body ...
與下面的代碼是一樣的:
class C(object):
def Methodmethod(self):
# method body ...
Methodmethod = MyDecoratormy_decorator(Methodmethod)
2.17.2 優(yōu)點
優(yōu)雅地指定方法上的某些轉(zhuǎn)換燃辖;這些轉(zhuǎn)換可以消除重復代碼鬼店,執(zhí)行約束條件等。
2.17.3 缺點
裝飾器可以對函數(shù)的參數(shù)或返回值執(zhí)行任意操作黔龟,可能導致令人意想不到的行為妇智。此外,裝飾器在引入的時候執(zhí)行氏身,如果裝飾器代碼執(zhí)行失敗巍棱,幾乎是不可能恢復的。
2.17.4 建議
有明顯好處時明智地使用裝飾器蛋欣。裝飾器應(yīng)當遵循跟函數(shù)一樣的引入和命名規(guī)則航徙。裝飾器的注釋文檔要明確說明該函數(shù)是一個裝飾器。為裝飾器編寫測試代碼陷虎。
在裝飾器內(nèi)部避免有外部依賴(例如到踏,不要依賴文件、socket尚猿、數(shù)據(jù)庫連接等)窝稿,因為在裝飾器運行(從pydoc或其他工具引入的時候)時它們可能不可用。使用有效參數(shù)調(diào)用的decorator應(yīng)該(盡可能多地)保證在所有情況下都能成功凿掂。
裝飾器“頂級代碼”的一種特殊情況伴榔,參考main獲取詳情。
永遠不要使用@staticmethod
(除非被迫與現(xiàn)有庫中的API進行集成),而是編寫一個模塊級函數(shù)潮梯。
只有在編寫命名構(gòu)造函數(shù)或特定于類的例程(該例程修改必要的全局狀態(tài)骗灶,如進程范圍的緩存)時才使用@classmethod。
2.18 Threading(線程)
不要依賴內(nèi)置類型的原子性秉馏。
盡管python的內(nèi)置數(shù)據(jù)類型(比如字典)看上去具有原子性操作耙旦,但是在有些情況下它們并不是原子性的(例如,如果__hash__
或者__eq__
實現(xiàn)為python方法)萝究,因此它們的原子性是靠不住的免都。同樣,你也不應(yīng)該依賴于原子性的變量賦值(因為該操作反過來依賴于字典)帆竹。
使用Queue模塊的Queue
數(shù)據(jù)類型作為線程間數(shù)據(jù)通信的優(yōu)選方式绕娘。不然就使用threading模塊以及它的locking其元。學習條件變量的恰當使用栽连,這樣就可以用threading.Condition
代替更低級別的鎖险领。
<a id="power-features"></a>
2.19 Power Features
避免這些特性。
2.19.1 定義
python是一種極具靈活性的語言秒紧,提供了許多有趣的特性绢陌,比如自定義原類、訪問字節(jié)碼熔恢、動態(tài)編譯脐湾、動態(tài)繼承、對象父類重定義(object reparenting)叙淌、導入hacks秤掌、反射及系統(tǒng)內(nèi)部構(gòu)件的修改等。
2.19.2 優(yōu)點
這些都是強有力的語言特性鹰霍。它們可以讓你的代碼更緊湊闻鉴。
2.19.3 缺點
使用這些“炫酷”的特性很有誘惑力,盡管它們不是絕對必要的茂洒。底層使用不常用特性的代碼變得更難以閱讀孟岛、理解和調(diào)試。一開始似乎不是這樣(對代碼原作者來說)获黔,但是再次訪問時,這些代碼往往比那些更長但是更直接的代碼更難理解在验。
2.19.4 建議
在你的代碼中避免出現(xiàn)這些特性玷氏。
推薦使用標準庫模塊和內(nèi)部使用這些特性的類(例如,abc.ABCMeta
腋舌、collections.namedtuple
和enum
)盏触。
2.20 Modern Python: Python 3 and from __future__ imports {#modern-python}
python3已經(jīng)發(fā)行了。盡管不是每個項目都是用python3,但是所有的代碼都應(yīng)該以面向未來的眼光來寫赞辩。
2.20.1 定義
python3是python語言中一個意義重大的改變雌芽。雖然現(xiàn)有的代碼多數(shù)是用2.7寫的,但是有一些簡單的事情可以做來讓代碼更明確地表達它的意圖个少。這樣可以更充分地準備好不做任何改變就能在python3下運行拥刻。
2.20.2 優(yōu)點
一旦項目的所有依賴都準備好了勉吻,用python3寫的代碼更明顯、更容易在python3下執(zhí)行屉佳。
2.20.3 缺點
許多人發(fā)現(xiàn)額外的樣板文件很難看。其他人可能說洲押,“我不在這個文件中使用該特性”武花,并希望進行清理。請不要這樣做杈帐。最好在所有文件中使用future imports体箕,這樣后來想使用這樣的特性而編輯的時候不會忘記。
2.20.4 建議
from __future__ imports
推薦使用from __future__ import
聲明挑童。所有新代碼應(yīng)該包含下面的語句累铅。如果可能的話,現(xiàn)有代碼應(yīng)該更新兼容性炮沐。
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
如果你還不熟悉這些争群,請參考: absolute imports、new / division behavior和the print function大年。
也有其他from __future__
聲明换薄。如果合適也可以使用。我們不建議unicode_literals
翔试,因為它在python2.7中很多地方引入的隱式默認編碼轉(zhuǎn)換序列并沒有明顯優(yōu)勢轻要。大多數(shù)代碼顯式使用b''
、u''
字節(jié)和必要的Unicode字符串常量更好垦缅。
當前冲泥、將來和過去的庫。
如果你的項目需要在python2和python3都支持壁涎,如果合適的話推薦使用這些庫凡恍。它們是你的代碼更簡潔。
2.21 Type Annotated Code(類型注釋代碼)
你可以根據(jù)PEP-484使用類型提示注釋python3代碼怔球,使用類型檢查工具(比如pytype)在編譯時對代碼進行類型檢查嚼酝。
類型注釋可以在源代碼或stub pyi file中。不論什么時候竟坛,盡可能把注釋放在源代碼中闽巩。在第三方或擴展模塊中使用pyi files钧舌。
2.21.1 定義
類型注釋(或類型提示)是針對于函數(shù)或方法參數(shù)和返回值的:
def func(a: int) -> List[int]:
你可以用特定的注釋聲明變量的類型:
a = SomeFunc() # type: SomeType
2.21.2 優(yōu)點
類型提示提高了代碼的可讀性和可維護性。類型檢查會將很多運行時錯誤轉(zhuǎn)換為編譯時錯誤涎跨,并減低你是使用Power Features的能力洼冻。
2.21.3 缺點
必須保持類型聲明是最新的。許多你認為有效的代碼可能有類型錯誤隅很。使用類型檢查可以降低你使用Power Features的能力撞牢。
2.21.4 建議
這高度依賴于項目的復雜度,試一試外构。