Python高能小技巧:用海象操作符减少重复代码
導(dǎo)讀:賦值表達式(assignment expression)是Python 3.8新引入的語法,它會用到海象操作符(walrus operator)。這種寫法可以解決某些持續(xù)已久的代碼重復(fù)問題。a = b是一條普通的賦值語句,讀作a equals b,而a := b則是賦值表達式,讀作a walrus b。
這個符號為什么叫walrus呢?因為把:=順時針旋轉(zhuǎn)90°之后,冒號就是海象的一雙眼睛,等號就是它的一對獠牙。
作者:布雷特·斯拉特金(Brett Slatkin)
來源:大數(shù)據(jù)DT(ID:hzdashuju)
這種表達式很有用,可以在普通的賦值語句無法應(yīng)用的場合實現(xiàn)賦值,例如可以用在條件表達式的if語句里面。賦值表達式的值,就是賦給海象操作符左側(cè)那個標(biāo)識符的值。
舉個例子。如果有一筐新鮮水果要給果汁店做食材,那我們就可以這樣定義其中的內(nèi)容:
fresh_fruit?=?{'apple':?10,'banana':?8,'lemon':?5, }顧客點檸檬汁之前,我們先得確認(rèn)現(xiàn)在還有沒有檸檬可以榨汁。所以,要先查出檸檬的數(shù)量,然后用if語句判斷它是不是非零的值。
def?make_lemonade(count):print(f'Making?{count}?lemons?into?lemonade')def?out_of_stock():print('Out?of?stock!')count?=?fresh_fruit.get('lemon',?0) if?count:make_lemonade(count) else:out_of_stock()這段代碼看上去雖然簡單,但還是顯得有點兒松散,因為count變量雖然定義在整個if/else結(jié)構(gòu)之上,然而只有if語句才會用到它,else塊根本就不需要使用這個變量。所以,這種寫法讓人誤以為count是個重要的變量,if和else都要用到它,但實際上并非如此。
我們在Python里面經(jīng)常要先獲取某個值,然后判斷它是否非零,如果是就執(zhí)行某段代碼。對于這種用法,我們以前總是要通過各種技巧,來避免count這樣的變量重復(fù)出現(xiàn)在代碼之中,這些技巧有時會讓代碼變得比較難懂。Python引入賦值表達式正是為了解決這樣的問題。下面改用海象操作符來寫:
if?count?:=?fresh_fruit.get('lemon',?0):make_lemonade(count) else:out_of_stock()新代碼雖然只省了一行,但讀起來卻清晰很多,因為這種寫法明確體現(xiàn)出count變量只與if塊有關(guān)。這個賦值表達式先把:=右邊的值賦給左邊的count變量,然后對自身求值,也就是把變量的值當(dāng)成整個表達式的值。
由于表達式緊跟著if,程序會根據(jù)它的值是否非零來決定該不該執(zhí)行if塊。這種先賦值再判斷的做法,正是海象操作符想要表達的意思。
檸檬汁效力強,所以只需要一顆檸檬就能做完這份訂單,這意味著程序只需判斷非零即可。如果客人點的是蘋果汁,那就至少得用四個蘋果才行。按照傳統(tǒng)的寫法,要先從fresh_fruit這個字典里面查出蘋果(apple)的數(shù)量(count),然后在if語句里,根據(jù)這個數(shù)量構(gòu)造條件表達式(count >= 4)。
def?make_cider(count):print(f'Making?cider?with?{count}?apples')count?=?fresh_fruit.get('apple',?0) if?count?>=?4:make_cider(count) else:out_of_stock()這段代碼與剛才那個檸檬汁的例子一樣,也過分突出了count變量的意義。下面改用海象操作符,把代碼寫得更清晰一些。
if?(count?:=?fresh_fruit.get('apple',?0))?>=?4:make_cider(count) else:out_of_stock()與剛才那個例子一樣,修改之后的代碼也比原來少了一行。但是這次,我們還要注意另外一個現(xiàn)象:賦值表達式本身是放在一對括號里面的。為什么要這樣做呢?因為我們要在if語句里面把這個表達式的結(jié)果跟4這個值相比較。
剛才檸檬汁的例子沒有加括號,因為那時只憑賦值表達式本身的值就能決定if/else的走向:只要表達式的值不是0,程序就進入if分支。但是這次不行,這次要把這個賦值表達式放在更大的表達式里面,所以必須用括號把它括起來。當(dāng)然,在沒有必要加括號的情況下,還是盡量別加括號比較好。
還有一種類似的邏輯也會出現(xiàn)剛才說的重復(fù)代碼,這指的是:我們要根據(jù)情況給某個變量賦予不同的值,緊接著要用這個變量做參數(shù)來調(diào)用某個函數(shù)。
例如,若顧客要點香蕉冰沙,那我們首先得把香蕉切成好幾份,然后用其中的兩份來制作這道冰沙。如果不夠兩份,那就拋出香蕉不足(OutOfBananas)異常。下面用傳統(tǒng)的寫法實現(xiàn)這種邏輯:
def?slice_bananas(count):print(f'Slicing?{count}?bananas')return?count?*?4class?OutOfBananas(Exception):passdef?make_smoothies(count):print(f'Making?a?smoothies?with?{count}?banana?slices')pieces?=?0 count?=?fresh_fruit.get('banana',?0) if?count?>=?2:pieces?=?slice_bananas(count)try:smoothies?=?make_smoothies(pieces) except?OutOfBananas:out_of_stock()還有一種傳統(tǒng)的寫法也很常見,就是把if/else結(jié)構(gòu)上方那條pieces = 0的賦值語句移動到else塊中。
count?=?fresh_fruit.get('banana',?0) if?count?>=?2:pieces?=?slice_bananas(count) else:pieces?=?0try:smoothies?=?make_smoothies(pieces) except?OutOfBananas:out_of_stock()這種寫法看上去稍微有點兒怪,因為if與else這兩個分支都給pieces變量定義了初始值。根據(jù)Python的作用域規(guī)則,這種分別定義變量初始值的寫法是成立的。雖說成立,但這樣寫看起來比較別扭,所以很多人喜歡用第一種寫法,也就是在進入if/else結(jié)構(gòu)之前,先把pieces的初始值給設(shè)置好。
改用海象操作符來實現(xiàn),可以少寫一行代碼,而且能夠壓低count變量的地位,讓它只出現(xiàn)在if塊里,這樣我們就能更清楚地意識到pieces變量才是整段代碼的重點。
pieces?=?0 if?(count?:=?fresh_fruit.get('banana',?0))?>=?2:pieces?=?slice_bananas(count)try:smoothies?=?make_smoothies(pieces) except?OutOfBananas:out_of_stock()對于在if與else分支里面分別定義pieces變量的寫法來說,海象操作符也能讓代碼變得清晰,因為這次不用再把count變量放到整個if/else塊的上方了。
if?(count?:=?fresh_fruit.get('banana',?0))?>=?2:pieces?=?slice_bananas(count) else:pieces?=?0try:smoothies?=?make_smoothies(pieces) except?OutOfBananas:out_of_stock()Python新手經(jīng)常會遇到這樣一種困難,就是找不到好辦法來實現(xiàn)switch/case結(jié)構(gòu)。最接近這種結(jié)構(gòu)的做法是在if/else結(jié)構(gòu)里面繼續(xù)嵌套if/else結(jié)構(gòu),或者使用if/elif/else結(jié)構(gòu)。
例如,我們想按照一定的順序自動給客人制作飲品,這樣就不用點餐了。下面這段邏輯先判斷能不能做香蕉冰沙,如果不能,就做蘋果汁,還不行,就做檸檬汁:
count?=?fresh_fruit.get('banana',?0) if?count?>=?2:pieces?=?slice_bananas(count)to_enjoy?=?make_smoothies(pieces) else:count?=?fresh_fruit.get('apple',?0)if?count?>=?4:to_enjoy?=?make_cider(count)else:count?=?fresh_fruit.get('lemon',?0)if?count:to_enjoy?=?make_lemonade(count)else:to_enjoy?=?'Nothing'這種難看的寫法其實在Python代碼里特別常見。幸好現(xiàn)在有了海象操作符,讓我們能夠輕松地模擬出很接近switch/case的方案。
if?(count?:=?fresh_fruit.get('banana',?0))?>=?2:pieces?=?slice_bananas(count)to_enjoy?=?make_smoothies(pieces) elif?(count?:=?fresh_fruit.get('apple',?0))?>=?4:to_enjoy?=?make_cider(count) elif?count?:=?fresh_fruit.get('lemon',?0):to_enjoy?=?make_lemonade(count) else:to_enjoy?=?'Nothing'這個版本只比原來短五行,但是看起來卻清晰得多,因為嵌套深度與縮進層數(shù)都變少了。只要碰到剛才那種難看的結(jié)構(gòu),我們就應(yīng)該考慮能不能改用海象操作符來寫。
Python新手還會遇到一個困難,就是缺少do/while循環(huán)結(jié)構(gòu)。例如,我們要把新來的水果做成果汁并且裝到瓶子里面,直到水果用完為止。下面先用普通的while循環(huán)來實現(xiàn):
FRUIT_TO_PICK?=?[{'apple':?1,?'banana':?3},{'lemon':?2,?'lime':?5},{'orange':?3,?'melon':?2}, ]def?pick_fruit():if?FRUIT_TO_PICK:return?FRUIT_TO_PICK.pop(0)else:return?[]def?make_juice(fruit,?count):return?[(fruit,?count)]bottles?=?[] fresh_fruit?=?pick_fruit() while?fresh_fruit:for?fruit,?count?in?fresh_fruit.items():batch?=?make_juice(fruit,?count)bottles.extend(batch)fresh_fruit?=?pick_fruit()print(bottles)這種寫法必須把fresh_fruit = pick_fruit()寫兩次,第一次是在進入while循環(huán)之前,因為我們要給fresh_fruit設(shè)定初始值,第二次是在while循環(huán)體的末尾,因為我們得把下一輪需要處理的水果列表填充到fresh_fruit里面。
如果想復(fù)用這行代碼,可以考慮loop-and-a-half 模式。這個模式雖然能消除重復(fù),但是會讓while循環(huán)看起來很笨,因為它成了無限循環(huán),程序只能通過break語句跳出這個循環(huán)。
FRUIT_TO_PICK?=?[{'apple':?1,?'banana':?3},{'lemon':?2,?'lime':?5},{'orange':?3,?'melon':?2}, ]bottles?=?[] while?True:?????????????????????#?Loopfresh_fruit?=?pick_fruit()if?not?fresh_fruit:?????????#?And?a?halfbreakfor?fruit,?count?in?fresh_fruit.items():batch?=?make_juice(fruit,?count)bottles.extend(batch)print(bottles)有了海象操作符,就不需要使用loop-and-a-half模式了,我們可以在每輪循環(huán)的開頭給fresh_fruit變量賦值,并根據(jù)變量的值來決定要不要繼續(xù)循環(huán)。這個寫法簡單易讀,所以應(yīng)該成為首選方案。
FRUIT_TO_PICK?=?[{'apple':?1,?'banana':?3},{'lemon':?2,?'lime':?5},{'orange':?3,?'melon':?2}, ]bottles?=?[] while?fresh_fruit?:=?pick_fruit():for?fruit,?count?in?fresh_fruit.items():batch?=?make_juice(fruit,?count)bottles.extend(batch)print(bottles)在其他一些場合,賦值表達式也能縮減重復(fù)代碼。總之,如果某個表達式或賦值操作多次出現(xiàn)在一組代碼里面,那就可以考慮用賦值表達式把這段代碼改得簡單一些。
要點
賦值表達式通過海象操作符(:=)給變量賦值,并且讓這個值成為這條表達式的結(jié)果,于是,我們可以利用這項特性來縮減代碼。
如果賦值表達式是大表達式里的一部分,就得用一對括號把它括起來。
雖說Python不支持switch/case與do/while結(jié)構(gòu),但可以利用賦值表達式清晰地模擬出這種邏輯。
關(guān)于作者:布雷特·斯拉特金(Brett Slatkin),Google首席軟件工程師,他是Google Surveys的聯(lián)合技術(shù)創(chuàng)始人,也是PubSubHubbub協(xié)議的共同創(chuàng)造者之一。此外,Slatkin還發(fā)布了Google的第一個云計算產(chǎn)品——App Engine。早在15年前,Slatkin就開始在工作中使用Python管理Google大量的服務(wù)器群。他擁有紐約哥倫比亞大學(xué)計算機工程專業(yè)學(xué)士學(xué)位。
本文摘編自《Effective Python:編寫高質(zhì)量Python代碼的90個有效方法》(原書第2版),經(jīng)出版方授權(quán)發(fā)布。
延伸閱讀《Effective Python》(原書第2版)
點擊上圖了解及購買
轉(zhuǎn)載請聯(lián)系微信:DoctorData
推薦語:Python編程進階必讀,基于Python3.8,新增31條建議!掌握Pythonic編程方式,寫出高質(zhì)量代碼|進階到編程高手的程序員修煉之道和代碼整潔之道。
劃重點????
干貨直達????
Pandas最詳細教程來了!
數(shù)據(jù)從哪里找?手把手教你構(gòu)建數(shù)據(jù)集
只有22%的人做對了這道數(shù)據(jù)分析題,你來試試嗎?
數(shù)據(jù)中臺:不是產(chǎn)品,不是技術(shù),到底是什么?(附完整PPT下載)
更多精彩????
在公眾號對話框輸入以下關(guān)鍵詞
查看更多優(yōu)質(zhì)內(nèi)容!
PPT?|?讀書?|?書單?|?硬核?|?干貨?|?講明白?|?神操作
大數(shù)據(jù)?|?云計算?|?數(shù)據(jù)庫?|?Python?|?可視化
AI?|?人工智能?|?機器學(xué)習(xí)?|?深度學(xué)習(xí)?|?NLP
5G?|?中臺?|?用戶畫像?|?1024?|?數(shù)學(xué)?|?算法?|?數(shù)字孿生
據(jù)統(tǒng)計,99%的大咖都關(guān)注了這個公眾號
????
總結(jié)
以上是生活随笔為你收集整理的Python高能小技巧:用海象操作符减少重复代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 双十一图书大数据:金庸第1,Python
- 下一篇: 人工智能自拍之后,会怎样给自己P图?