Python实践提升-条件分支控制流

Python实践提升-条件分支控制流
从某种角度来看,编程这件事,其实就是把真实世界里的逻辑用代码的方式书写出来。

而真实世界里的逻辑通常很复杂,包含许许多多先决条件和结果分支,无法用一句简单的“因为……所以……”来概括。如果画成地图,这些逻辑不会是只有几条高速公路的郊区,而更像是包含无数个岔路口的闹市区。

为了表现这些真实世界里的复杂逻辑,程序员们写出了一条条分支语句。比如简单的“如果用户是会员,跳过广告播放”:

if user.is_active_member():
    skip_ads()
    return True
else:
    print('你不是会员,无法跳过广告。')
    return False

或者复杂一些的:

if user.is_active_member():
    if user.membership_expires_in(30):
        print('会员将在 30 天内过期,请及时续费,将在 3 秒后跳过广告')
        skip_ads_with_delay(3)
        return True

    skip_ads()
    return True
elif user.region != 'CN':
    print('非中国区无法跳过广告')
    return False
else:
    print('你不是会员,无法跳过广告。')
    return False

当条件分支变得越来越复杂,代码的可读性也会变得越来越差。所以,掌握如何写出好的条件分支代码非常重要,它可以帮助我们用更简洁、更清晰的代码来表达复杂逻辑。本章将会谈谈如何在 Python 中写出更好的条件分支代码。

4.1 基础知识
4.1.1 分支惯用写法
  在 Python 里写条件分支语句,听上去是件挺简单的事。这是因为严格说来 Python 只有一种条件分支语法——if/elif/else1:

1在 Python 3.10 版本发布后,这个说法其实已不再成立。Python 在 3.10 版本里引入了一种新的分支控制结构:结构化模式匹配(structural pattern matching)。这种新结构启用了 match/case 关键字,实现了类似 C 语言中的 switch/case 语法。但和传统 switch 语句比起来,Python 的模式匹配功能要强大得多(语法也复杂得多)。因为本书的编写环境是 Python 3.8,所以我不会对“结构化模式匹配”做太多介绍。如果你对它感兴趣,可以阅读 PEP-634 了解更多内容。

#标准条件分支语句
if condition:
    ...
elif another_condition:
    ...
else:
    ...

当我们编写分支时,第一件要注意的事情,就是不要显式地和布尔值做比较:

#不推荐的写法
#if user.is_active_member() == True:

#推荐写法
if user.is_active_member():

绝大多数情况下,在分支判断语句里写 == True 都没有必要,删掉它代码会更短也更易读。但这条原则也有例外,比如你确实想让分支仅当值是 True 时才执行。不过即便这样,写 if == True 仍然是有问题的,我会在 4.1.3 节解释这一点。

省略零值判断

当你编写 if 分支时,如果需要判断某个类型的对象是否是零值,可能会把代码写成下面这样:

if containers_count == 0:
    ...

if fruits_list != []:
    ...

这种判断语句其实可以变得更简单,因为当某个对象作为主角出现在 if 分支里时,解释器会主动对它进行“真值测试”,也就是调用 bool() 函数获取它的布尔值。而在计算布尔值时,每类对象都有着各自的规则,比如整型和列表的规则如下:

#数字 0 的布尔值为 False,其他值为 True
>>> bool(0), bool(123)
(False, True)

#空列表的布尔值为 False,其他值为 True
>>> bool([]), bool([1, 2, 3])
(False, True)

正因如此,当我们需要在条件语句里做空值判断时,可以直接把代码简写成下面这样:

if not containers_count:
    ...

if fruits_list:
    ...

这样的条件判断更简洁,也更符合 Python 社区的习惯。不过在你使用这种写法时,请不要忘记一点,这样写其实隐晦地放宽了分支判断的成立条件:

#更精准:只有为 0 的时候,才会满足分支条件
if containers_count == 0:
    ....

#更宽泛:当 containers_count 的值为 0、None、空字符串等时,都可以满足分支条件
if not containers_count:
    ...

请时刻注意,不要因为过度追求简写而引入其他逻辑问题。

除整型外,其他内置类型的布尔值规则如下。

布尔值为假:None、0、False、[]、()、{}、set()、frozenset(),等等。
布尔值为真:非 0 的数值、True,非空的序列、元组、字典,用户定义的类和实例,等等。

把否定逻辑移入表达式内

在构造布尔逻辑表达式时,你可以用 not 关键字来表达“否定”含义:

>>> i = 10
>>> i > 8
True
>>> not i > 8
False

不过在写代码时,我们有时会过于喜欢用 not 关键字,反倒忘记了运算符本身就可以表达否定逻辑。最后,代码里会出现许多下面这种判断语句:

if not number < 10:
    ...

if not current_user is None:
    ...

if not index == 1:
    ...

这样的代码,就好比你在看到一个人沿着楼梯往上走时,不说“他在上楼”,而非说“他在做和下楼相反的事情”。如果把否定逻辑移入表达式内,它们通通可以改成下面这样:

if number >= 10:
    ...

if current_user is not None:
    ...

if index != 1:
    ...

这样的代码逻辑表达得更直接,也更好理解。

尽可能让三元表达式保持简单

除了标准分支外,Python 还为我们提供了一种浓缩版的条件分支——三元表达式:

#语法:
#true_value if <expression> else false_value
language = "python" if you.favor("dynamic") else "golang"

当你在编写三元表达式时,请参考 3.3.6 节的两个“不要”里的建议,不要盲目追求用一个表达式来表达过于复杂的逻辑。有时,平淡普通的分支语句远远胜过花哨复杂的三元表达式。

4.1.2 修改对象的布尔值
  上一节提过,当我们把某个对象用于分支判断时,解释器会对它进行“真值测试”,计算出它的布尔值,而所有用户自定义的类和类实例的计算结果都是 True:

>>> class Foo:
...     pass
...
>>> bool(Foo)
True
>>> bool(Foo())
True

这个现象符合逻辑,但有时会显得有点儿死板。如果我们稍微改动一下这个默认行为,就能写出更优雅的代码。

看看下面这个例子:

class UserCollection:
    """用于保存多个用户的集合工具类"""

    def __init__(self, users):
        self.items = users

users = UserCollection(['piglei', 'raymond'])

#仅当用户列表里面有数据时,打印语句
if len(users.items) > 0:
    print("There's some users in collection!")

在上面这段代码里,我需要判断 users 对象是否真的有内容,因此里面的分支判断语句用到了 len(users.items) > 0 这样的表达式:判断对象内 items 的长度是否大于 0。

但其实,上面的分支判断语句可以变得更简单。只要给 UserCollection 类实现 len 魔法方法,users 对象就可以直接用于“真值测试”:

class UserCollection:
    """用于保存多个用户的集合工具类"""

    def __init__(self, users):
        self.items = users

    def __len__(self):
        return len(self.items)

users = UserCollection(['piglei', 'raymond'])

#不再需要手动判断对象内部 items 的长度
if users:
    print("There's some users in collection!")

为类定义 len 魔法方法,实际上就是为它实现了 Python 世界的长度协议:

>>> users = UserCollection([])
>>> len(users)
0
>>> users = UserCollection(['piglei', 'raymond'])
>>> len(users)
2

Python 在计算这类对象的布尔值时,会受 len(users) 的结果影响——假如长度为 0,布尔值为 False,反之为 True。因此当例子中的 UserCollection 类实现了 len 后,整个条件判断语句就得到了简化。

不过,定义 len 并非影响布尔值结果的唯一办法。除了 len 以外,还有一个魔法方法 bool 和对象的布尔值息息相关。

为对象定义 bool 方法后,对它进行布尔值运算会直接返回该方法的调用结果。举个例子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值