Python編程規(guī)范規(guī)范修煉-Google編程規(guī)范解讀

規(guī)范修煉-Google編程規(guī)范解讀

Guido van Rossum(吉多·范羅蘇姆,Python 創(chuàng)始人 )說(shuō)過(guò),代碼的閱讀頻率遠(yuǎn)高于編寫代碼的頻率。畢竟,即使是在編寫代碼的時(shí)候,你也需要對(duì)代碼進(jìn)行反復(fù)閱讀和調(diào)試,來(lái)確認(rèn)代碼能夠按照期望運(yùn)行。

接下來(lái)我們今天就為大家分享《Python編程規(guī)范:讓你的代碼腳下生風(fēng)》第三彈《Google開源項(xiàng)目風(fēng)格指南》

Python是Google主要的腳本語(yǔ)言。這本風(fēng)格指南主要包含的是針對(duì)python的編程規(guī)范。

Google開源項(xiàng)目風(fēng)格指南-Python風(fēng)格指南包含以下兩個(gè)主要內(nèi)容

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

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

文檔地址: Google開源項(xiàng)目風(fēng)格指南

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

在Google的Python風(fēng)格規(guī)范中主要涉及了像如何使用縮進(jìn)、空格和代碼行的長(zhǎng)度、注釋、文檔以及老生常談的命名規(guī)范、分號(hào)的使用、導(dǎo)入格式等,這些基本上在前面的分享中都提及過(guò),這里就不在過(guò)多解釋。

但是在Google風(fēng)格規(guī)范中提及了Main的使用,我認(rèn)為這個(gè)還是比較重要的內(nèi)容。

首先我們知道Python語(yǔ)音的一個(gè)特點(diǎn)就是模塊與包,我們可以在程序中導(dǎo)入第三方模塊或者包,當(dāng)然也可以在項(xiàng)目中自定義模塊與包,這樣的做法會(huì)讓我們把一些功能的實(shí)現(xiàn)進(jìn)行封裝,剩下的只需要關(guān)注我們當(dāng)前的業(yè)務(wù)代碼即可。

因此我們?cè)谀K文件或者自定義模塊時(shí)中總是能夠看到或使用下面這樣的語(yǔ)句。

def main():
     print('run')

if __name__ == '__main__':
    main()

‘’‘
__name__ 是當(dāng)前模塊名,當(dāng)模塊被直接運(yùn)行時(shí)模塊名為 ‘__main__’
一般我們會(huì)在模塊測(cè)試時(shí),把相關(guān)的調(diào)用或者執(zhí)行放在`if __name__ == '__main__'`當(dāng)中。
這樣可以保證我們的測(cè)試代碼不會(huì)在模塊被導(dǎo)入時(shí)執(zhí)行,而只是在模塊被作為主程序時(shí)執(zhí)行。
’‘’

這是一種很好的特性,而我們?cè)谶M(jìn)行模塊開發(fā)時(shí)也變的更加方便,那么接下來(lái)我們看一下在Google編碼規(guī)范中時(shí)如何對(duì)Main的使用進(jìn)行規(guī)范化的。

Main

即使是一個(gè)打算被用作腳本的文件, 也應(yīng)該是可導(dǎo)入的. 并且簡(jiǎn)單的導(dǎo)入不應(yīng)該導(dǎo)致這個(gè)腳本的主功能(main functionality)被執(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():
     print('run')

if __name__ == '__main__':
    main()

所有的頂級(jí)代碼在模塊導(dǎo)入時(shí)都會(huì)被執(zhí)行.

要小心不要去調(diào)用函數(shù), 創(chuàng)建對(duì)象, 或者執(zhí)行那些不應(yīng)該在使用pydoc時(shí)執(zhí)行的操作.

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

接下來(lái)我們一起看下《Google開源項(xiàng)目風(fēng)格指南》中對(duì)Python語(yǔ)言的一些規(guī)范吧

1.Lint

對(duì)你的代碼運(yùn)行pylint,pylint是一個(gè)在Python源代碼中查找bug的工具.

可以捕獲容易忽視的錯(cuò)誤, 例如輸入錯(cuò)誤, 使用未賦值的變量等.

2.導(dǎo)入

僅對(duì)包和模塊使用導(dǎo)入.并且必要時(shí)使用as

3.包

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

4.異常

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

總結(jié)幾點(diǎn)如下:

  • 永遠(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.
  • 盡量減少try/except塊中的代碼量. try塊的體積越大, 期望之外的異常就越容易被觸發(fā). 這種情況下, try/except塊將隱藏真正的錯(cuò)誤.
  • 使用finally子句來(lái)執(zhí)行那些無(wú)論try塊中有沒有異常都應(yīng)該被執(zhí)行的代碼. 這對(duì)于清理資源常常很有用, 例如關(guān)閉文件.

5.全局變量

避免全局變量,導(dǎo)入時(shí)可能改變模塊行為, 因?yàn)閷?dǎo)入模塊時(shí)會(huì)對(duì)模塊級(jí)變量賦值.

避免使用全局變量, 用類變量來(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).

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

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

定義:

  • 類可以定義在方法, 函數(shù)或者類中.
  • 函數(shù)可以定義在方法或函數(shù)中.
  • 封閉區(qū)間中定義的變量對(duì)嵌套函數(shù)是只讀的.

優(yōu)點(diǎn): 允許定義僅用于有效范圍的工具類和函數(shù).

缺點(diǎn): 嵌套類或局部類的實(shí)例不能序列化(pickled).

結(jié)論: 推薦使用.

7.列表推導(dǎo)(List Comprehensions)

可以在簡(jiǎn)單情況下使用,復(fù)雜的列表推導(dǎo)或者生成器表達(dá)式可能難以閱讀.

先看結(jié)論

簡(jiǎn)單的列表推導(dǎo)可以比其它的列表創(chuàng)建方法更加清晰簡(jiǎn)單.

但是復(fù)雜的列表推導(dǎo)或者生成器表達(dá)式可能難以閱讀.

因此

列表推導(dǎo)適用于簡(jiǎn)單情況. 每個(gè)部分應(yīng)該單獨(dú)置于一行: 映射表達(dá)式, for語(yǔ)句, 過(guò)濾器表達(dá)式.

禁止多重for語(yǔ)句或過(guò)濾器表達(dá)式. 復(fù)雜情況下還是使用循環(huán).

下面是一些代碼示例,可以感受一下

# 適用于簡(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)


# 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.默認(rèn)迭代器和操作符

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

優(yōu)點(diǎn):

  • 默認(rèn)操作符和迭代器簡(jiǎn)單高效, 它們直接表達(dá)了操作, 沒有額外的方法調(diào)用.

  • 使用默認(rèn)操作符的函數(shù)是通用的. 它可以用于支持該操作的任何類型.

缺點(diǎn):

  • 你沒法通過(guò)閱讀方法名來(lái)區(qū)分對(duì)象的類型(例如, has_key()意味著字典). 不過(guò)這也是優(yōu)點(diǎ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(): ...

9.生成器

按需使用生成器.

生成器的定義:

所謂生成器函數(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)存更少.

鼓勵(lì)使用. 注意在生成器函數(shù)的文檔字符串中使用”Yields:”而不是”Returns:”.

10.Lambda函數(shù)

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

優(yōu)點(diǎn):方便。

缺點(diǎn):比本地函數(shù)更難閱讀和調(diào)試. 沒有函數(shù)名意味著堆棧跟蹤更難理解. 由于lambda函數(shù)通常只包含一個(gè)表達(dá)式, 因此其表達(dá)能力有限.

結(jié)論:適用于單行函數(shù). 如果代碼超過(guò)60-80個(gè)字符, 最好還是定義成常規(guī)(嵌套)函數(shù).

11.條件表達(dá)式

條件表達(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ǔ)句.

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

適用于大部分情況。

# 不要在函數(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...
         ...

結(jié)論:鼓勵(lì)使用,但需要注意:不要在函數(shù)或方法定義中使用可變對(duì)象作為默認(rèn)值.

13.True/False的求值

盡可能使用隱式false,

Python在布爾上下文中會(huì)將某些值求值為false. 按簡(jiǎn)單的直覺來(lái)講, 就是所有的”空”值都被認(rèn)為是false.

因此0, None, [], {}, “” 都被認(rèn)為是false.

優(yōu)點(diǎn): 使用Python布爾值的條件語(yǔ)句更易讀也更不易犯錯(cuò). 大部分情況下, 也更快.

結(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` . 

# 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()
        
# 注意‘0’(字符串)會(huì)被當(dāng)做true.

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

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

優(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í)代碼”.

15.線程

優(yōu)先使用Queue模塊的 Queue 數(shù)據(jù)類型作為線程間的數(shù)據(jù)通信方式.

Python的Queue模塊中提供了同步的、線程安全的隊(duì)列類,包括FIFO(先入先出)隊(duì)列Queue,LIFO(后入先出)隊(duì)列LifoQueue,和優(yōu)先級(jí)隊(duì)列PriorityQueue。

這些隊(duì)列都實(shí)現(xiàn)了鎖原語(yǔ),能夠在多線程中直接使用??梢允褂藐?duì)列來(lái)實(shí)現(xiàn)線程間的同步。

16.威力過(guò)大的特性

避免使用這些威力過(guò)大的特性

定義:

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)試.
  • 開始可能還好, 但當(dāng)你回顧代碼, 它們可能會(huì)比那些稍長(zhǎng)一點(diǎn)但是很直接的代碼更加難以理解.

結(jié)論:莫裝B

Google-Python編碼規(guī)范 總結(jié)

在上面的內(nèi)容中我們對(duì)Python中的Main的使用以及針對(duì)Python語(yǔ)言特性的語(yǔ)法規(guī)范進(jìn)行了解讀,那么希望小伙伴們通過(guò)本期《Google-Python編碼規(guī)范》以及上一期《Python編程規(guī)范修煉-PEP8規(guī)范解讀》的文章對(duì)Python編程的規(guī)范有了一個(gè)很深的認(rèn)知,如果對(duì)本期關(guān)于編碼規(guī)范的內(nèi)容進(jìn)行一個(gè)總結(jié)的話

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

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

以上就關(guān)于分期的分享,在后面我們還會(huì)為大家分享關(guān)于Python代碼安全、精簡(jiǎn)語(yǔ)句、調(diào)試和性能分析以及代碼分解和單元測(cè)試等Python編程規(guī)范的系列內(nèi)容,感興趣的小伙伴歡迎關(guān)注我的公眾號(hào)后廠程序員。

如果喜歡或者對(duì)你有幫助的小伙伴,歡迎大家關(guān)注我的公眾號(hào):后廠程序員,并分享、點(diǎn)贊、在看 三連

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容