Python 里的分支代碼
Python 支持最為常見的 if/else
條件分支語(yǔ)句,不過(guò)它缺少在其他編程語(yǔ)言中常見的 switch/case
語(yǔ)句。
除此之外,Python 還為 for/while
循環(huán)以及 try/except
語(yǔ)句提供了 else 分支,在一些特殊的場(chǎng)景下,它們可以大顯身手。
1. 避免多層分支嵌套
如果這篇文章只能刪減成一句話就結(jié)束,那么那句話一定是“要竭盡所能的避免分支嵌套”。
過(guò)深的分支嵌套是很多編程新手最容易犯的錯(cuò)誤之一。假如有一位新手 JavaScript 程序員寫了很多層分支嵌套,那么你可能會(huì)看到一層又一層的大括號(hào):if { if { if { ... }}}
。俗稱“嵌套 if 地獄(Nested If Statement Hell)”。
但是因?yàn)?Python 使用了縮進(jìn)來(lái)代替 {}
,所以過(guò)深的嵌套分支會(huì)產(chǎn)生比其他語(yǔ)言下更為嚴(yán)重的后果。比如過(guò)多的縮進(jìn)層次很容易就會(huì)讓代碼超過(guò) PEP8 中規(guī)定的每行字?jǐn)?shù)限制。讓我們看看這段代碼:
def buy_fruit(nerd, store):
"""去水果店買蘋果
- 先得看看店是不是在營(yíng)業(yè)
- 如果有蘋果的話,就買 1 個(gè)
- 如果錢不夠,就回家取錢再來(lái)
"""
if store.is_open():
if store.has_stocks("apple"):
if nerd.can_afford(store.price("apple", amount=1)):
nerd.buy(store, "apple", amount=1)
return
else:
nerd.go_home_and_get_money()
return buy_fruit(nerd, store)
else:
raise MadAtNoFruit("no apple in store!")
else:
raise MadAtNoFruit("store is closed!")
上面這段代碼最大的問(wèn)題,就是過(guò)于直接翻譯了原始的條件分支要求,導(dǎo)致短短十幾行代碼包含了有三層嵌套分支。
這樣的代碼可讀性和維護(hù)性都很差。不過(guò)我們可以用一個(gè)很簡(jiǎn)單的技巧:“提前結(jié)束” 來(lái)優(yōu)化這段代碼:
def buy_fruit(nerd, store):
if not store.is_open():
raise MadAtNoFruit("store is closed!")
if not store.has_stocks("apple"):
raise MadAtNoFruit("no apple in store!")
if nerd.can_afford(store.price("apple", amount=1)):
nerd.buy(store, "apple", amount=1)
return
else:
nerd.go_home_and_get_money()
return buy_fruit(nerd, store)
“提前結(jié)束”指:在函數(shù)內(nèi)使用 return
或 raise
等語(yǔ)句提前在分支內(nèi)結(jié)束函數(shù)。比如,在新的 buy_fruit
函數(shù)里,當(dāng)分支條件不滿足時(shí),我們直接拋出異常,結(jié)束這段這代碼分支。這樣的代碼沒有嵌套分支,更直接也更易讀。
2. 封裝那些過(guò)于復(fù)雜的邏輯判斷
如果條件分支里的表達(dá)式過(guò)于復(fù)雜,出現(xiàn)了太多的 not/and/or
,那么這段代碼的可讀性就會(huì)大打折扣,比如下面這段代碼:
# 如果活動(dòng)還在開放,并且活動(dòng)剩余名額大于 10,為所有性別為女性,或者級(jí)別大于 3
# 的活躍用戶發(fā)放 10000 個(gè)金幣
if activity.is_active and activity.remaining > 10 and \
user.is_active and (user.sex == 'female' or user.level > 3):
user.add_coins(10000)
return
對(duì)于這樣的代碼,我們可以考慮將具體的分支邏輯封裝成函數(shù)或者方法,來(lái)達(dá)到簡(jiǎn)化代碼的目的:
if activity.allow_new_user() and user.match_activity_condition():
user.add_coins(10000)
return
事實(shí)上,將代碼改寫后,之前的注釋文字其實(shí)也可以去掉了。因?yàn)楹竺孢@段代碼已經(jīng)達(dá)到了自說(shuō)明的目的。至于具體的 什么樣的用戶滿足活動(dòng)條件? 這種問(wèn)題,就應(yīng)由具體的 match_activity_condition()
方法來(lái)回答了。
Hint: 恰當(dāng)?shù)姆庋b不光直接改善了代碼的可讀性,事實(shí)上,如果上面的活動(dòng)判斷邏輯在代碼中出現(xiàn)了不止一次的話,封裝更是必須的。不然重復(fù)代碼會(huì)極大的破壞這段邏輯的可維護(hù)性。
3. 留意不同分支下的重復(fù)代碼
重復(fù)代碼是代碼質(zhì)量的天敵,而條件分支語(yǔ)句又非常容易成為重復(fù)代碼的重災(zāi)區(qū)。所以,當(dāng)我們編寫條件分支語(yǔ)句時(shí),需要特別留意,不要生產(chǎn)不必要的重復(fù)代碼。
讓我們看下這個(gè)例子:
# 對(duì)于新用戶,創(chuàng)建新的用戶資料,否則更新舊資料
if user.no_profile_exists:
create_user_profile(
username=user.username,
email=user.email,
age=user.age,
address=user.address,
# 對(duì)于新建用戶,將用戶的積分置為 0
points=0,
created=now(),
)
else:
update_user_profile(
username=user.username,
email=user.email,
age=user.age,
address=user.address,
updated=now(),
)
在上面的代碼中,我們可以一眼看出,在不同的分支下,程序調(diào)用了不同的函數(shù),做了不一樣的事情。但是,因?yàn)槟切┲貜?fù)代碼的存在,我們卻很難簡(jiǎn)單的區(qū)分出,二者的不同點(diǎn)到底在哪。
其實(shí),得益于 Python 的動(dòng)態(tài)特性,我們可以簡(jiǎn)單的改寫一下上面的代碼,讓可讀性可以得到顯著的提升:
if user.no_profile_exists:
profile_func = create_user_profile
extra_args = {'points': 0, 'created': now()}
else:
profile_func = update_user_profile
extra_args = {'updated': now()}
profile_func(
username=user.username,
email=user.email,
age=user.age,
address=user.address,
**extra_args
)
當(dāng)你編寫分支代碼時(shí),請(qǐng)額外關(guān)注由分支產(chǎn)生的重復(fù)代碼塊,如果可以簡(jiǎn)單的消滅它們,那就不要遲疑。
4. 謹(jǐn)慎使用三元表達(dá)式
三元表達(dá)式是 Python 2.5 版本后才支持的語(yǔ)法。在那之前,Python 社區(qū)一度認(rèn)為三元表達(dá)式?jīng)]有必要,我們需要使用 x and a or b
的方式來(lái)模擬它。[注]
事實(shí)是,在很多情況下,使用普通的 if/else
語(yǔ)句的代碼可讀性確實(shí)更好。盲目追求三元表達(dá)式很容易誘惑你寫出復(fù)雜、可讀性差的代碼。
所以,請(qǐng)記得只用三元表達(dá)式處理簡(jiǎn)單的邏輯分支。
language = "python" if you.favor("dynamic") else "golang"
對(duì)于絕大多數(shù)情況,還是使用普通的 if/else
語(yǔ)句吧。
審核編輯:湯梓紅
-
代碼
+關(guān)注
關(guān)注
30文章
4788瀏覽量
68625 -
python
+關(guān)注
關(guān)注
56文章
4797瀏覽量
84694
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論