编写高质量Python(第10条) 用赋值表达式减少重复代码

第10条 用赋值表达式减少重复代码

​ 赋值表达式是 Pyhton3.8 新引入的语法,它会用到海象操作符。这种写法可以解决某些持续已久的代码重复问题。a = b 是一条普通的赋值语句,读作 a equals b,而 a := b 则是赋值表达式,读作 a walrus b。(这个符号为什么叫 walrus 呢?因为把 := 顺时针旋转90度之后,冒号就是一双海象的一双眼睛,等号就是它的一对獠牙)。

​ 这种表达式很有用,可以在普通的赋值语句无法应用的场合实现赋值,例如可以用在条件表达式的 if 语句里面。赋值表达式的值,就是赋给海象操作符左侧那个标志符的值。

​ 举个例子。如果有一筐新鲜水果要给果汁店做食材,那么我们就可以定义其中的内容:

fresh_fruit = {
    'apple': 10,
    'banana': 8,
    'lemon': 5,
}

​ 顾客点柠檬汁之前,我们先得确认现在还有没有柠檬可以榨汁。所以,要先查出柠檬的数量,然后用 if 语句判断它是不是非零的值。

def make_lemon(count):
    ...

def out_of_stock():
    ...

count = fresh_fruit.get('lemon', 0)
if count:
    make_lemon(count)
else:
    out_of_stock()

​ 这段代码看起来虽然简单,但是显得有点儿松散,因为 count 变量虽然定义在整个 if/else 结构之上,然而只有 if 语句才会用它,else 块根本就不需要使用这个变量。所以,这种写法让人误认为 count 是个重要的变量,if 和 else 都要用到它,但事实并非如此。

​ 我们在 Python 里面经常要先获取某个值,然后判断它是否为零,如果是就执行某段代码。对于这种用法,我们以前总是要通过各种技巧,来避免 count 这样的变量重复出现在现在代码中,这些技巧有时变得比较难懂(参考 第5条 里面提到的那些技巧)。Python 引入赋值表达式正是为了解决这样的问题。下面改用海象操作符来写:

count = fresh_fruit.get('lemon', 0)
if count:
    make_lemon(count)
else:
    out_of_stock()

新代码虽然只省了一行,但读起来比较清晰很多,因为这种写法明确体现出 count 变量只与 if 块有关。这个赋值表达式先把 := 右边的值。由于表达式紧跟着 if ,程序会根据它的值是否非零来决定该不该执行 if 块。这种先赋值在判断的做法,正是海象操作符想要表达的意思。

​ 柠檬汁效力强,所以只需要一颗就能做完这份订单,这意味着程序只需判断非零即可。

​ 如果客人点的是苹果汁,那就至少得用四个苹果才行。按照传统的写法,要先从 fresh_fruit 这个字典里面查出苹果 (apple) 的数量 (count),然后在 if 语句里,根据这个数量构造条件表达式。(count >= 4)

def make_cider(count):
    ...
    
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()

​ 与刚才的例子一样,修改之后的代码比原来少了一行。但是这次,我们还要注意另外一个现象:赋值表达式本身是放在一对括号里面的。为什么要这样做呢?因为我们要在 if 语句里面把表达式结果跟 4 这个值相比较。刚才柠檬汁的例子没有加括号,因为那是只凭值表达式本身的值就能决定 if/else 的走向:只要表达式的值不是 0,程序就进入 if 分支。但是这次不太行,这次要把这个赋值表达式放在更大的表达式里面,所以必须用括号把它给括起来。

​ 还有一种类似的逻辑情况也会出现刚才说的那种重复代码,这指的是:我们要根据情况给某个变量赋予不同的值,紧接着要用这个变量做参数来调用某个函数。

​ 例如,顾客想点香蕉冰沙,那我们首先要把香蕉切成好几份,然后用其中两份来制作这道冰沙。如果不够两份,那就要抛出香蕉不足(OutOfBananas)异常。下面用传统的写法来实现这种逻辑:

def slice_bananas(count):
    ...

class OutOfBananas(Exception):
    pass

def make_smoothies(count):
    ...

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()

​ 还有一种传统的写法也很常见,就是把 if/else 结构上方那条 pieces = 0 的赋值语句移动到 else 块中。

count = fresh_fruit.get('banana', 0)
if count >= 2:
    pieces = slice_bananas(count)
else:
    pieces = 0
    
try:
    smoothies = make_smoothies(pieces)
except OutOfBananas:
    out_of_stock()

​ 这种写法看上去有点奇怪,因为 if 和 else 这两个分支都给 pieces 变量定义了初始值。根据 Python 到作用域规则,这种分别定义变量初始值的写法是成立的(参见 第21条)。

​ 改用海象操作符来实现,可以少写一行代码,而且能够压低 count 变量的地位,让它只能在出现在 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)):
    pieces = slice_bananas(count)

else:
    pieces = 0
    
try:
    smoothies = make_smoothies(count)
except OutOfBananas:
    out_of_stock()

​ Python 新手经常遇到这样一些困难,就是找不到好方法来实现 switch/case 结构。最接近这种结构的做法是在 if/else 结构里面继续嵌套 if/else 结构,或者使用 if/elif/else 结构。

​ 例如,我们想按照一定的顺序自动给客人制造饮品,这样就不会点餐了。下面这段逻辑先判断能不能做香蕉冰沙,如果不能,就做苹果汁,如果还不行,就做柠檬汁:

count = fresh_fruit.get('banana', 0)
if count >= 2:
    pieces = slice_bananas(count)
    to_enjoy = make_smoothies(count)
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_lemon(count)
        else:
            to_enjoy = 'Nothing'

​ 这种难看的写法其实在 Python 代码里特别常见。幸好有了海象操作符,让我们能够轻松模拟出 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_lemon(count)
else:
    to_enjoy = 'Nothing'

​ 这个版本只比原本少了短短五行,但是看起来却清晰得多,因为嵌套深度和缩进层数都变少了。所以我们可以用海象操作符来改写刚才的那种结构。

​ Python 新手还会遇到一个困难,就是缺少 do/while 循环结构。例如,我们要把新来的水果做成果汁并且装到瓶子里面,直到水果用完为止。下面用普通的 while 循环来实现:

def pick_fruit():
    return {}


def make_juice(fruit, count):
    return []


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()

​ 这种写法必须把 fresh_fruit = pick_fruit() 写两次,第一次是在进入 while 循环之前,因为我们要给 fresh_fruit 设定初始值,第二次是在 while 循环体的末尾,因为我们得把下一轮要处理的水果列表填充到 fresh_fruit 里面。

​ 如果想复用这行代码,可以考虑 loop-and-a-half 模式。(在循环体执行到一半时,判断要不要跳出)这个模式虽然消除重复,但是会让整个while 循环看起来很笨,因为它做成了无限循环,程序只能通过 break 来退出。

bottles = []
while True:
    fresh_fruit = pick_fruit()
    if not fresh_fruit:
        break
    for fruit, count in fresh_fruit.items():
        batch = make_juice(fruit, count)
        bottles.append(batch)

​ 有了海象操作符,就不需要使用 loop-and-a-half 模式了,我们可以在每轮循环的开头给 fresh_fruit 变量赋值,并根据变量的值来决定要不要继续循环。

bottles = []
while fresh_fruit := pick_fruit():
    for fruit, count in fresh_fruit.items():
        batch = make_juice(fruit, count)
        bottles.append(batch)
	在其他一些场合,赋值表达式也能缩减重复代码(参见 第29条)。总之,如果这个表达式或复制操作多次出现在一组代码里面,那就可以考虑用赋值表达式来把这段代码改得简单一些。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值