30.4.3. 用赋值表达式消除推导中的重复代码

推导list、dict与set等变体结构时,经常要在多个地方用到同一个计算结果。

例如,我们要给制作紧固件的公司编写程序以管理订单。顾客下单后,我们要判断当前的库存能否满足这份订单,也就是说,要核查每种产品的数量有没有达到可以发货的最低限制(8个为一批,至少要有一批,才能发货)。

#!/usr/bin/env python
# -*- coding:utf8 -*-
stock = {
    "nails": 125,
    "screws": 35,
    "wingnuts": 8,
    "washers": 24,
}

order = ["screws", "wingunts", "clips"]

def get_batches(count, size):
    return count // size

result = {}

for name in order:
    count = stock.get(name, 0)
    batches = get_batches(count, 8)
    if batches:
        result[name] = batches

print(result)

这段循环逻辑,如果改用字典推导来写,会简单一些

found = {name: get_batches(stock.get(name, 0), 8)
         for name in order
         if get_batches(stock.get(name, 0), 8)}
print(found)

这样写虽然比刚才简短,但问题是,它把get_batches(stock.get(name, 0), 8)写了两遍。这样会让代码看起来比较乱,而且实际上,程序也没有必要把这个结果计算两遍。另外,如果这两个地方忘了同步更新,那么程序就会出现bug。

有个简单的办法可以解决这个问题,那就是在推导的过程中使用Python 3.8新引入的:=操作符进行赋值表达

found = {name: batches for name in order
         if (batches := get_batches(stock.get(name, 0), 8))}

这条batches := get_batches(…)赋值表达式,能够从stock字典里查到对应产品一共有几批,并把这个批数放在batches变量里。

这样的话,我们推导这个产品所对应批数时,就不用再通过get_batches计算了,因为结果已经保存到batches里面了。

这种写法只需要把get与get_batches调用一次即可,这样能够提升效率,因为我们不需要针对order列表中的每件产品都多做一次get与get_batches。

赋值表达式不仅可以用在推导过程中,而且可以用来编写生成器表达式(generator expression)

found = ((name, batches) for name in order
         if (batches := get_batches(stock.get(name, 0), 8)))
print(next(found))
print(next(found))

要点:

  • 编写推导式与生成器表达式时,可以在描述条件的那一部分通过赋值表达式定义变量,并在其他部分复用该变量,可使程序简单易读。

  • 对于推导式与生成器表达式来说,虽然赋值表达式也可以出现在描述条件的那一部分之外,但最好别这么写。