Python 支持最为常见的 if/else
条件分支语句,不过它缺少在其他编程语言中常见的 switch/case
语句。
除此之外,Python 还为 for/while
循环以及 try/except
语句提供了 else 分支,在一些特殊的场景下,它们可以大显身手。
如果这篇文章只能删减成一句话就结束,那么那句话一定是“要竭尽所能的避免分支嵌套”。
过深的分支嵌套是很多编程新手最容易犯的错误之一。假如有一位新手 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
函数里,当分支条件不满足时,我们直接抛出异常,结束这段这代码分支。这样的代码没有嵌套分支,更直接也更易读。
如果条件分支里的表达式过于复杂,出现了太多的 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: 恰当的封装不光直接改善了代码的可读性,事实上,如果上面的活动判断逻辑在代码中出现了不止一次的话,封装更是必须的。不然重复代码会极大的破坏这段逻辑的可维护性。
重复代码是代码质量的天敌,而条件分支语句又非常容易成为重复代码的重灾区。所以,当我们编写条件分支语句时,需要特别留意,不要生产不必要的重复代码。
让我们看下这个例子:
# 对于新用户,创建新的用户资料,否则更新旧资料
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
)
当你编写分支代码时,请额外关注由分支产生的重复代码块,如果可以简单的消灭它们,那就不要迟疑。
三元表达式是 Python 2.5 版本后才支持的语法。在那之前,Python 社区一度认为三元表达式没有必要,我们需要使用 x and a or b
的方式来模拟它。[注]
事实是,在很多情况下,使用普通的 if/else
语句的代码可读性确实更好。盲目追求三元表达式很容易诱惑你写出复杂、可读性差的代码。
所以,请记得只用三元表达式处理简单的逻辑分支。
language = "python" if you.favor("dynamic") else "golang"
对于绝大多数情况,还是使用普通的 if/else
语句吧。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !