翻譯《Writing Idiomatic Python》(一):if語句、for循環


開篇廢話

這是在美國Amazon上評價很不錯的一本書,其實嚴格來說這可能不算書,而是一本小冊子。就像書名一樣,里面的內容主要是用一些例子講述地道的Python的代碼是怎樣寫的。書中把很多例子用不良風格和地道Python寫法作對比,內容覆蓋談不上很全,但是每一條都很有代表性。總體而言非常適合新手,同時里面有些條目老手看了或許也會有豁然開朗的感覺。作者Jeff Knupp曾在全球最牛B的高盛和其他銀行里做過金融系統開發,在北美Python社區里也很有活躍度。

自己用Python也有些年頭了,做過一年多的商業開發,不過其他大部分還是以科研和預研期的算法為主。最近因為又開始用Python做商業開發,所以想着順便找些書看看,無意中看到了這本小書,覺得很不錯,國內沒有賣的,更別提中文版了。翻譯這本書,算是復習和重新思考下Python,同時也會有少量自己的見解(C++風格注釋綠色粗體),希望能堅持下去吧。我看的版本主要分為四部分:Control Structures and Functions(控制結構和函數)、Working with Data(數據和類型)、Organizing Your Code(代碼組織)、General Advice(一般性建議)。每一部分里又分為不同的小章節,一共二十幾個。我會按這個順序不定期放出數目不定的章節。本人英文水平尚可,不過沒有翻譯經驗,雖然不知道會不會有人關注這個系列,還是希望如果有看官,請輕拍指正:)

原書參考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/

下一篇:翻譯《Writing Idiomatic Python》(二):函數、異常


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 避免將條件分支中的代碼和冒號放在同一行

使用縮進來表示代碼塊的結構會讓人更容易判斷條件分支的代碼結構。ifelifelse語句應該都總是獨占一行,在冒號后沒有代碼。

不良風格:

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 = False
2 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型而不是TrueFalseif語句在判斷是否為0時仍然正確。在更深的層面上,這是基於相等性(equality)和等價性(identity)的差別。使用==檢查的是兩個對象是否有相等或是等效的值(由_eq屬性定義),而is語句則檢查的是兩個對象在底層是否同一個對象。

// Python對相等的實現在C代碼中實現將比較對象用PyInt_AS_LONG轉化成long型,然后再用C中的==進行比較,而is的實現是直接==比較。

所以對FalseNone和和空的序列比如[], {},以及()應該避免直接進行比較。如果一個叫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這個參數沒有設置,因為 if 0: 會判定為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 = True
2 value = 0
3 
4 if foo:
5     value = 1
6 
7 print(value)

地道Python:

1 foo = True
2 
3 value = 1 if foo else 0
4 
5 print(value)

1.2 For循環

1.2.1 在循環中使用enumerate函數來創建計數或索引

在許多其他語言中,開發者習慣顯式地聲明一個變量用來作為循環中的計數或者相關容器的索引。例如在C++中:

1 for ( int i = 0; i < container.size(); ++i )
2 {
3     // Do stuff
4 }

在Python中,內置的enumerate函數就可以很自然地處理這種需要。

不良風格:

1 my_container = ['Larry', 'Moe', 'Curly']
2 index = 0
3 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 = 0
3 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             break
7     else:
8         print('所有email地址均有效!')

轉載請注明出處:達聞西@博客園

下一篇:翻譯《Writing Idiomatic Python》(二):函數、異常


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM