標籤:
開篇廢話
這是在美國Amazon上評價很不錯的一本書,其實嚴格來說這可能不算書,而是一本小冊子。就像書名一樣,裡面的內容主要是用一些例子講述地道的Python的代碼是怎樣寫的。書中把很多例子用不良風格和地道Python寫法作對比,內容覆蓋談不上很全,但是每一條都很有代表性。總體而言非常適合新手,同時裡面有些條目老手看了或許也會有豁然開朗的感覺。作者Jeff Knupp曾在全球最牛B的高盛和其他銀行裡做過金融系統開發,在北美Python社區裡也很有活躍度。
自己用Python也有些年頭了,做過一年多的商業開發,不過其他大部分還是以科研和預研期的演算法為主。最近因為又開始用Python做商業開發,所以想著順便找些書看看,無意中看到了這本小書,覺得很不錯,國內沒有賣的,更別提中文版了。翻譯這本書,算是複習和重新思考下Python,同時也會有少量自己的見解(C++風格注釋綠色粗體),希望能堅持下去吧。我看的版本主要分為四部分:Control Structures and Functions(控制結構和函數)、Working with Data(資料和類型)、Organizing Your Code(程式碼群組織)、General Advice(一般性建議)。每一部分裡又分為不同的小章節,一共二十幾個。我會按這個順序不定期放出數目不定的章節。本人英文水平尚可,不過沒有翻譯經驗,雖然不知道會不會有人關注這個系列,還是希望如果有看官,請輕拍指正:)
1. 控制結構和函數1.1 if語句1.1.1 通過鏈式比較讓語句更加簡明
當使用if語句時,優先使用鏈式比較操作,不僅會讓語句更加簡明,也會讓執行效率更好。
不良風格:
1 if x <= y and y <= z:2 return True
地道Python:
1 if x <= y <= z:2 return True
// Python解釋執行以上兩種不同的比較方式時,其實都是先比較x<=y,如果為真,再比較y<=z。主要的區別在於,鏈式比較時,會先取y的值,然後複製壓棧,整個過程中y的求值只執行了一次,而用and的方式時,y的求值會執行兩次,也就是說,如果比較的是三個函數或者複雜的對象的話,鏈式比較只會求值三次,而通過and比較的方式則會求值4次。這大概就是為什麼作者說執行效率會更好,但實際上如果只是簡單的變數進行比較,效率未必會有提高。
1.1.2 避免將條件分支中的代碼和冒號放在同一行
使用縮排來表示代碼塊的結構會讓人更容易判斷條件分支的代碼結構。if,elif和else語句應該都總是獨佔一行,在冒號後沒有代碼。
不良風格:
1 name = ‘Jeff‘2 address = ‘New York, NY‘3 4 if name: print(name)5 print(address)
地道Python:
1 name = ‘Jeff‘2 address = ‘New York, NY‘3 4 if name:5 print(name)6 print(address)
// 檔案中的代碼應該遵循這個規則,在控制台下放一行也未嘗不可
1.1.3 避免在複合的if語句中重複出現同一個變數名
當想用if語句檢查一個變數是否和許多值中的一個相等時,用==和or重複寫許多遍是否相等的檢查會顯得代碼很冗長。簡潔的寫法是判斷該變數是否在一個可遍曆的結構中。
不良風格:
1 is_generic_name = False2 name = ‘Tom‘3 if name == ‘Tom‘ or name == ‘Dick‘ or name == ‘Harry‘:4 is_generic_name = True
地道Python:
1 name = ‘Tom‘2 is_generic_name = name in (‘Tom‘, ‘Dick‘, ‘Harry‘)
1.1.4 避免直接與True, False或者None直接比較
對於任意Python中的對象,無論是內建的還是使用者定義的,本身都會關聯一個內部的“真值”(truthiness)。所以很自然地,當判斷一個條件是否為真的時候,盡量在條件判斷語句中優先依靠這個隱式的“真值”。下面列舉的是“真值”為False的情況:
None
False
數值0
空的序列(列表,元組等)
空的字典
當__len__或者__nonzero__被調用後返回的0值或者False
按照上面的最後一條,通過檢查調用__len__或者__nonzero__後返回的值的方式,我們也可以定義自己建立的類型的“真值”。除了上面列舉的這些,其他的情況都被認為“真值”為True。
在Python中if語句隱式地使用“真值”,所以你的代碼中也應該這樣做。比如對於下面這種寫法:
if foo == True:
更簡單而直接的寫法是:
if foo:
這樣做的理由有很多。最明顯的一條理由是,如果你的代碼發生了變化,比如當foo變成了一個int型而不是True或False,if語句在判斷是否為0時仍然正確。在更深的層面上,這是基於相等性(equality)和等價性(identity)的差別。使用==檢查的是兩個對象是否有相等或是等效的值(由_eq屬性定義),而is語句則檢查的是兩個對象在底層是否同一個對象。
// Python對相等的實現在C代碼中實現將比較對象用PyInt_AS_LONG轉化成long型,然後再用C中的==進行比較,而is的實現是直接==比較。
所以對False,None和和空的序列比如[], {},以及()應該避免直接進行比較。如果一個叫my_list的列表為空白, if my_list 會判斷為False。當然有些情況下,雖然不推薦,但是直接和None比較是必須的。當在一個函數中需要判斷一個預設值為None的參數是否被賦值的時候,比如:
1 def insert_value(value, position=None):2 """向自訂的容器中插入一個值,插入值3 的位置作為選擇性參數,預設值為None"""4 if position is not None:5 ...
如果使用 if position: 的話,哪裡會出錯呢?設想如果有人想在0位置插入一個值,那麼函數會認為position這個參數沒有設定,因為 if0: 會判定為False。注意這裡使用的是is not,根據PEP8,和None比較應該總是用is或者is not而不是==
總之,就讓Python的“真值”代替你做比較的工作。
不良風格:
1 def number_of_evil_robots_attacking(): 2 return 10 3 4 def should_raise_shields(): 5 # 只有當一隻以上的巨型機器人進攻時才開啟防護罩 6 # 所以我只需要返回巨型機器人的數量,如果不為零會自動判斷為真 7 return number_of_evil_robots_attacking() 8 9 if should_raise_shields() == True:10 raise_shields()11 print(‘防護罩已開啟‘)12 else:13 print(‘安全!並沒有巨型機器人在進攻‘)
地道Python:
1 def number_of_evil_robots_attacking(): 2 return 10 3 4 def should_raise_shields(): 5 # 只有當一隻以上的巨型機器人進攻時才開啟防護罩 6 # 所以我只需要返回巨型機器人的數量,如果不為零會自動判斷為真 7 return number_of_evil_robots_attacking() 8 9 if should_raise_shields():10 raise_shields()11 print(‘防護罩已開啟‘)12 else:13 print(‘安全!並沒有巨型機器人在進攻‘)
1.1.5 使用if 和 else作為三元操作符的替代
和許多其他語言不同,Python沒有三元操作符(比如: x ? true : false)。不過Python可以將賦值延遲到條件判斷之後,所以在Python中三元操作可以用條件判斷來替代。當然需要注意的是,除非是很簡單的語句,否則三元操作的替代方案會讓語句的可讀性降低。
不良風格:
1 foo = True2 value = 03 4 if foo:5 value = 16 7 print(value)
地道Python:
1 foo = True2 3 value = 1 if foo else 04 5 print(value)
1.2 For迴圈1.2.1 在迴圈中使用enumerate函數來建立計數或索引
在許多其他語言中,開發人員習慣顯式地聲明一個變數用來作為迴圈中的計數或者相關容器的索引。例如在C++中:
1 for ( int i = 0; i < container.size(); ++i )2 {3 // Do stuff4 }
在Python中,內建的enumerate函數就可以很自然地處理這種需要。
不良風格:
1 my_container = [‘Larry‘, ‘Moe‘, ‘Curly‘]2 index = 03 for element in my_container:4 print(‘{} {}‘.format(index, element))5 index += 1
地道Python:
1 my_container = [‘Larry‘, ‘Moe‘, ‘Curly‘]2 for index, element in enumerate(my_container):3 print(‘{} {}‘.format(index, element))
1.2.2 使用in關鍵字遍曆可迭代結構
在沒有for_each風格的語言中,開發人員習慣於用索引(下標)來遍曆一個容器中的元素。而在Python中,這種操作可以通過in關鍵字來更為優雅地實現。
不良風格:
1 my_list = [‘Larry‘, ‘Moe‘, ‘Curly‘]2 index = 03 while index < len(my_list):4 print(my_list[index])5 index += 1
地道Python:
1 my_list = [‘Larry‘, ‘Moe‘, ‘Curly‘]2 for element in my_list:3 print(element)
1.2.3 使用else去執行一個for迴圈全部遍曆結束後的代碼
在Python的for迴圈中可以包含一個else分句,這是一個不多人知道的技巧。else語句塊會在for迴圈中的迭代結束後執行,除非在迭代過程中迴圈因為break語句結束。利用這種寫法我們可以在迴圈中執行條件檢查。要麼在要檢查的條件陳述式為真時用break語句停止迴圈,要麼在迴圈結束後進入else語句塊並執行條件未被滿足的情況下要執行的動作。這樣做避免了在迴圈中單獨使用一個標示變數來檢查條件是否被滿足。
不良風格:
1 for user in get_all_users(): 2 has_malformed_email_address = False 3 print(‘檢查 {}‘.format(user)) 4 for email_address in user.get_all_email_addresses(): 5 if email_is_malformed(email_address): 6 has_malformed_email_address = True 7 print(‘包含惡意email地址!‘) 8 break 9 if not has_malformed_email_address:10 print(‘所有email地址均有效!‘)
地道Python:
1 for user in get_all_users():2 print(‘檢查 {}‘.format(user))3 for email_address in user.get_all_email_addresses():4 if email_is_malformed(email_address):5 print(‘包含惡意email地址!‘)6 break7 else:8 print(‘所有email地址均有效!‘)
翻譯《Writing Idiomatic Python》(一):if語句、for迴圈