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