規(guī)范修煉-Google編程規(guī)范解讀
Guido van Rossum(吉多·范羅蘇姆稍途,Python 創(chuàng)始人 )說過,代碼的閱讀頻率遠高于編寫代碼的頻率突勇。畢竟甲馋,即使是在編寫代碼的時候定躏,你也需要對代碼進行反復閱讀和調試芹敌,來確認代碼能夠按照期望運行氏捞。
接下來我們今天就為大家分享《Python編程規(guī)范:讓你的代碼腳下生風》第三彈《Google開源項目風格指南》
Python是Google主要的腳本語言。這本風格指南主要包含的是針對python的編程規(guī)范液茎。
Google開源項目風格指南-Python風格指南包含以下兩個主要內容
Python風格規(guī)范
Python語言規(guī)范
文檔地址: Google開源項目風格指南
一哼凯、Python風格規(guī)范
在Google的Python風格規(guī)范中主要涉及了像如何使用縮進、空格和代碼行的長度班缎、注釋她渴、文檔以及老生常談的命名規(guī)范沉唠、分號的使用满葛、導入格式等嘀韧,這些基本上在前面的分享中都提及過篇亭,這里就不在過多解釋。
但是在Google風格規(guī)范中提及了Main
的使用锄贷,我認為這個還是比較重要的內容译蒂。
首先我們知道Python語音的一個特點就是模塊與包,我們可以在程序中導入第三方模塊或者包谊却,當然也可以在項目中自定義模塊與包柔昼,這樣的做法會讓我們把一些功能的實現(xiàn)進行封裝,剩下的只需要關注我們當前的業(yè)務代碼即可因惭。
因此我們在模塊文件或者自定義模塊時中總是能夠看到或使用下面這樣的語句岳锁。
def main():
print('run')
if __name__ == '__main__':
main()
‘’‘
__name__ 是當前模塊名,當模塊被直接運行時模塊名為 ‘__main__’
一般我們會在模塊測試時蹦魔,把相關的調用或者執(zhí)行放在`if __name__ == '__main__'`當中激率。
這樣可以保證我們的測試代碼不會在模塊被導入時執(zhí)行低缩,而只是在模塊被作為主程序時執(zhí)行。
’‘’
這是一種很好的特性银觅,而我們在進行模塊開發(fā)時也變的更加方便洒忧,那么接下來我們看一下在Google編碼規(guī)范中時如何對Main的使用進行規(guī)范化的。
Main
即使是一個打算被用作腳本的文件, 也應該是可導入的. 并且簡單的導入不應該導致這個腳本的主功能(main functionality)被執(zhí)行, 這是一種副作用. 主功能應該放在一個main()函數(shù)中.
在Python中, pydoc以及單元測試要求模塊必須是可導入的.
你的代碼應該在執(zhí)行主程序前總是檢查 if __name__ == '__main__'
,
這樣當模塊被導入時主程序就不會被執(zhí)行.
def main():
print('run')
if __name__ == '__main__':
main()
所有的頂級代碼在模塊導入時都會被執(zhí)行.
要小心不要去調用函數(shù), 創(chuàng)建對象, 或者執(zhí)行那些不應該在使用pydoc時執(zhí)行的操作.
二减余、 Python語言規(guī)范
接下來我們一起看下《Google開源項目風格指南》中對Python語言的一些規(guī)范吧
1.Lint
對你的代碼運行pylint,pylint是一個在Python源代碼中查找bug的工具.
可以捕獲容易忽視的錯誤, 例如輸入錯誤, 使用未賦值的變量等.
2.導入
僅對包和模塊使用導入.并且必要時使用as
3.包
使用模塊的全路徑名來導入每個模塊
4.異常
允許使用異常, 但必須小心抒抬。
總結幾點如下:
- 永遠不要使用 except: 語句來捕獲所有異常, 也不要捕獲 Exception 或者 StandardError , 除非你打算重新觸發(fā)該異常, 或者你已經在當前線程的最外層(記得還是要打印一條錯誤消息). 在異常這方面, Python非常寬容, except: 真的會捕獲包括Python語法錯誤在內的任何錯誤. 使用 except: 很容易隱藏真正的bug.
- 盡量減少try/except塊中的代碼量. try塊的體積越大, 期望之外的異常就越容易被觸發(fā). 這種情況下, try/except塊將隱藏真正的錯誤.
- 使用finally子句來執(zhí)行那些無論try塊中有沒有異常都應該被執(zhí)行的代碼. 這對于清理資源常常很有用, 例如關閉文件.
5.全局變量
避免全局變量,導入時可能改變模塊行為, 因為導入模塊時會對模塊級變量賦值.
避免使用全局變量, 用類變量來代替. 但也有一些例外:
- 腳本的默認選項.
- 模塊級常量. 例如: PI = 3.14159. 常量應該全大寫, 用下劃線連接.
- 有時候用全局變量來緩存值或者作為函數(shù)返回值很有用.
- 如果需要, 全局變量應該僅在模塊內部可用, 并通過模塊級的公共函數(shù)來訪問.
6.嵌套/局部/內部類或函數(shù)
鼓勵使用嵌套/本地/內部類或函數(shù)
定義:
- 類可以定義在方法, 函數(shù)或者類中.
- 函數(shù)可以定義在方法或函數(shù)中.
- 封閉區(qū)間中定義的變量對嵌套函數(shù)是只讀的.
優(yōu)點: 允許定義僅用于有效范圍的工具類和函數(shù).
缺點: 嵌套類或局部類的實例不能序列化(pickled).
結論: 推薦使用.
7.列表推導(List Comprehensions)
可以在簡單情況下使用,復雜的列表推導或者生成器表達式可能難以閱讀.
先看結論
簡單的列表推導可以比其它的列表創(chuàng)建方法更加清晰簡單.
但是復雜的列表推導或者生成器表達式可能難以閱讀.
因此
列表推導適用于簡單情況. 每個部分應該單獨置于一行: 映射表達式, for語句, 過濾器表達式.
禁止多重for語句或過濾器表達式. 復雜情況下還是使用循環(huán).
下面是一些代碼示例,可以感受一下
# 適用于簡單情況.
# 每個部分應該單獨置于一行: 映射表達式, for語句, 過濾器表達式.
# 禁止多重for語句或過濾器表達式. 復雜情況下還是使用循環(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)
# 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)
8.默認迭代器和操作符
如果類型支持, 就使用默認迭代器和操作符. 比如列表, 字典及文件等.
優(yōu)點:
默認操作符和迭代器簡單高效, 它們直接表達了操作, 沒有額外的方法調用.
使用默認操作符的函數(shù)是通用的. 它可以用于支持該操作的任何類型.
缺點:
- 你沒法通過閱讀方法名來區(qū)分對象的類型(例如, has_key()意味著字典). 不過這也是優(yōu)點.
# 內建類型也定義了迭代器方法. 優(yōu)先考慮這些方法, 而不是那些返回列表的方法.
# 當然族铆,這樣遍歷容器時献丑,你將不能修改容器.
# 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(): ...
9.生成器
按需使用生成器.
生成器的定義:
所謂生成器函數(shù), 就是每當它執(zhí)行一次生成(yield)語句, 它就返回一個迭代器, 這個迭代器生成一個值.
生成值后, 生成器函數(shù)的運行狀態(tài)將被掛起, 直到下一次生成.
優(yōu)點:簡化代碼, 因為每次調用時, 局部變量和控制流的狀態(tài)都會被保存. 比起一次創(chuàng)建一系列值的函數(shù), 生成器使用的內存更少.
鼓勵使用. 注意在生成器函數(shù)的文檔字符串中使用”Yields:”而不是”Returns:”.
10.Lambda函數(shù)
適用于單行函數(shù).
優(yōu)點:方便莽红。
缺點:比本地函數(shù)更難閱讀和調試. 沒有函數(shù)名意味著堆棧跟蹤更難理解. 由于lambda函數(shù)通常只包含一個表達式, 因此其表達能力有限.
結論:適用于單行函數(shù). 如果代碼超過60-80個字符, 最好還是定義成常規(guī)(嵌套)函數(shù).
11.條件表達式
條件表達式是對于if語句的一種更為簡短的句法規(guī)則. 例如: x = 1 if cond else 2
.
優(yōu)點:比if語句更加簡短和方便.
缺點:比if語句難于閱讀. 如果表達式很長网棍, 難于定位條件.
結論:適用于單行函數(shù). 在其他情況下惑畴,推薦使用完整的if語句.
12.默認參數(shù)值
適用于大部分情況杠袱。
# 不要在函數(shù)或方法定義中使用可變對象作為默認值.
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...
...
結論:鼓勵使用菩彬,但需要注意:不要在函數(shù)或方法定義中使用可變對象作為默認值.
13.True/False的求值
盡可能使用隱式false,
Python在布爾上下文中會將某些值求值為false. 按簡單的直覺來講, 就是所有的”空”值都被認為是false.
因此0耙旦, None, [], {}, “” 都被認為是false.
優(yōu)點: 使用Python布爾值的條件語句更易讀也更不易犯錯. 大部分情況下, 也更快.
結論: 盡可能使用隱式的false, 例如: 使用 if foo:而不是 if foo != []:
不過還是有一些注意事項需要你銘記在心:
# 1. 永遠不要用==或者!=來比較單件, 比如None. 使用is或者is not.
# 2. 注意: 當你寫下 `if x:` 時, 你其實表示的是 `if x is not None` .
# 3. 永遠不要用==將一個布爾量與false相比較. 使用 `if not x:` 代替.
# 如果你需要區(qū)分false和None, 你應該用像 `if not x and x is not None:` 這樣的語句.
# 4. 對于序列(字符串, 列表, 元組), 要注意空序列是false.
# 因此 `if not seq:` 或者 `if seq:` 比 `if len(seq):` 或 `if not len(seq):` 要更好.
# 5. 處理整數(shù)時, 使用隱式false可能會得不償失(即不小心將None當做0來處理).
# 你可以將一個已知是整型(且不是len()的返回結果)的值與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()
# 注意‘0’(字符串)會被當做true.
14.函數(shù)與方法裝飾器
如果好處很顯然, 就明智而謹慎的使用裝飾器
優(yōu)點:優(yōu)雅的在函數(shù)上指定一些轉換. 該轉換可能減少一些重復代碼, 保持已有函數(shù)不變(enforce invariants), 等.
缺點:
- 裝飾器可以在函數(shù)的參數(shù)或返回值上執(zhí)行任何操作, 這可能導致讓人驚異的隱藏行為.
- 而且, 裝飾器在導入時執(zhí)行.
- 從裝飾器代碼的失敗中恢復更加不可能.
結論: 如果好處很顯然, 就明智而謹慎的使用裝飾器,但注意使用
- 裝飾器應該遵守和函數(shù)一樣的導入和命名規(guī)則.
- 裝飾器的python文檔應該清晰的說明該函數(shù)是一個裝飾器.
- 請為裝飾器編寫單元測試.
- 避免裝飾器自身對外界的依賴(即不要依賴于文件, socket, 數(shù)據庫連接等), 因為裝飾器運行時這些資源可能不可用(由
pydoc
或其它工具導入). - 應該保證一個用有效參數(shù)調用的裝飾器在所有情況下都是成功的.
- 裝飾器是一種特殊形式的”頂級代碼”.
15.線程
優(yōu)先使用Queue模塊的
Queue
數(shù)據類型作為線程間的數(shù)據通信方式.
Python的Queue模塊中提供了同步的险领、線程安全的隊列類臭笆,包括FIFO(先入先出)隊列Queue,LIFO(后入先出)隊列LifoQueue椒拗,和優(yōu)先級隊列PriorityQueue腋舌。
這些隊列都實現(xiàn)了鎖原語世落,能夠在多線程中直接使用圆凰∈徽樱可以使用隊列來實現(xiàn)線程間的同步回怜。
16.威力過大的特性
避免使用這些威力過大的特性
定義:
Python是一種異常靈活的語言, 它為你提供了很多花哨的特性, 諸如元類(metaclasses), 字節(jié)碼訪問, 任意編譯(on-the-fly compilation), 動態(tài)繼承, 對象父類重定義(object reparenting), 導入黑客(import hacks), 反射, 系統(tǒng)內修改(modification of system internals), 等等.
優(yōu)點:
- 強大的語言特性, 能讓你的代碼更緊湊.
缺點:
- 使用這些很”酷”的特性十分誘人, 但不是絕對必要. 使用它們將更加難以閱讀和調試.
- 開始可能還好, 但當你回顧代碼, 它們可能會比那些稍長一點但是很直接的代碼更加難以理解.
結論:莫裝B
Google-Python編碼規(guī)范 總結
在上面的內容中我們對Python中的Main的使用以及針對Python語言特性的語法規(guī)范進行了解讀,那么希望小伙伴們通過本期《Google-Python編碼規(guī)范》以及上一期《Python編程規(guī)范修煉-PEP8規(guī)范解讀》的文章對Python編程的規(guī)范有了一個很深的認知,如果對本期關于編碼規(guī)范的內容進行一個總結的話
請務必保持代碼的一致性
如果你正在編輯代碼, 花幾分鐘看一下周邊代碼, 然后決定風格. 比如如果它們在所有的算術操作符兩邊都使用空格, 那么你也應該這樣做. 如果它們的注釋都用標記包圍起來, 那么你的注釋也要這樣.總之保持風格的統(tǒng)一才是王道
以上就關于分期的分享冲泥,在后面我們還會為大家分享關于Python代碼安全浮还、精簡語句延刘、調試和性能分析以及代碼分解和單元測試等Python編程規(guī)范的系列內容砰嘁,感興趣的小伙伴歡迎關注我的公眾號后廠程序員斟冕。
如果喜歡或者對你有幫助的小伙伴,歡迎大家關注我的公眾號:后廠程序員缅阳,并分享磕蛇、點贊、在看 三連