Python高能小技巧:用海象操作符减少重复代码

时间:2022-09-06 07:50:26

a = b是一条普通的赋值语句,读作a equals b,而a := b则是赋值表达式,读作a walrus b。这个符号为什么叫walrus呢?因为把:=顺时针旋转90°之后,冒号就是海象的一双眼睛,等号就是它的一对獠牙。

Python高能小技巧:用海象操作符减少重复代码

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

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

  1. fresh_fruit = { 
  2.     'apple': 10, 
  3.     'banana': 8, 
  4.     'lemon': 5, 

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

  1. def make_lemonade(count): 
  2.     print(f'Making {count} lemons into lemonade'
  3.  
  4. def out_of_stock(): 
  5.     print('Out of stock!'
  6.  
  7. count = fresh_fruit.get('lemon', 0) 
  8. if count
  9.     make_lemonade(count
  10. else
  11.     out_of_stock() 

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

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

  1. if count := fresh_fruit.get('lemon', 0): 
  2.     make_lemonade(count
  3. else
  4.     out_of_stock() 

新代码虽然只省了一行,但读起来却清晰很多,因为这种写法明确体现出count变量只与if块有关。这个赋值表达式先把:=右边的值赋给左边的count变量,然后对自身求值,也就是把变量的值当成整个表达式的值。

由于表达式紧跟着if,程序会根据它的值是否非零来决定该不该执行if块。这种先赋值再判断的做法,正是海象操作符想要表达的意思。

柠檬汁效力强,所以只需要一颗柠檬就能做完这份订单,这意味着程序只需判断非零即可。如果客人点的是苹果汁,那就至少得用四个苹果才行。按照传统的写法,要先从fresh_fruit这个字典里面查出苹果(apple)的数量(count),然后在if语句里,根据这个数量构造条件表达式(count >= 4)。

  1. def make_cider(count): 
  2.     print(f'Making cider with {count} apples'
  3.  
  4. count = fresh_fruit.get('apple', 0) 
  5. if count >= 4: 
  6.     make_cider(count
  7. else
  8.     out_of_stock() 

这段代码与刚才那个柠檬汁的例子一样,也过分突出了count变量的意义。下面改用海象操作符,把代码写得更清晰一些。

  1. if (count := fresh_fruit.get('apple', 0)) >= 4: 
  2.     make_cider(count
  3. else
  4.     out_of_stock() 

与刚才那个例子一样,修改之后的代码也比原来少了一行。但是这次,我们还要注意另外一个现象:赋值表达式本身是放在一对括号里面的。为什么要这样做呢?因为我们要在if语句里面把这个表达式的结果跟4这个值相比较。

刚才柠檬汁的例子没有加括号,因为那时只凭赋值表达式本身的值就能决定if/else的走向:只要表达式的值不是0,程序就进入if分支。但是这次不行,这次要把这个赋值表达式放在更大的表达式里面,所以必须用括号把它括起来。当然,在没有必要加括号的情况下,还是尽量别加括号比较好。

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

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

  1. def slice_bananas(count): 
  2.     print(f'Slicing {count} bananas'
  3.     return count * 4 
  4.  
  5. class OutOfBananas(Exception): 
  6.     pass 
  7.  
  8. def make_smoothies(count): 
  9.     print(f'Making a smoothies with {count} banana slices'
  10.  
  11. pieces = 0 
  12. count = fresh_fruit.get('banana', 0) 
  13. if count >= 2: 
  14.     pieces = slice_bananas(count
  15.  
  16. try: 
  17.     smoothies = make_smoothies(pieces) 
  18. except OutOfBananas: 
  19.     out_of_stock() 

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

  1. count = fresh_fruit.get('banana', 0) 
  2. if count >= 2: 
  3.     pieces = slice_bananas(count
  4. else
  5.     pieces = 0 
  6.  
  7. try: 
  8.     smoothies = make_smoothies(pieces) 
  9. except OutOfBananas: 
  10.     out_of_stock() 

这种写法看上去稍微有点儿怪,因为if与else这两个分支都给pieces变量定义了初始值。根据Python的作用域规则,这种分别定义变量初始值的写法是成立的。虽说成立,但这样写看起来比较别扭,所以很多人喜欢用第一种写法,也就是在进入if/else结构之前,先把pieces的初始值给设置好。

改用海象操作符来实现,可以少写一行代码,而且能够压低count变量的地位,让它只出现在if块里,这样我们就能更清楚地意识到pieces变量才是整段代码的重点。

  1. pieces = 0 
  2. if (count := fresh_fruit.get('banana', 0)) >= 2: 
  3.     pieces = slice_bananas(count
  4.  
  5. try: 
  6.     smoothies = make_smoothies(pieces) 
  7. except OutOfBananas: 
  8.     out_of_stock() 

对于在if与else分支里面分别定义pieces变量的写法来说,海象操作符也能让代码变得清晰,因为这次不用再把count变量放到整个if/else块的上方了。

  1. if (count := fresh_fruit.get('banana', 0)) >= 2: 
  2.     pieces = slice_bananas(count
  3. else
  4.     pieces = 0 
  5.  
  6. try: 
  7.     smoothies = make_smoothies(pieces) 
  8. except OutOfBananas: 
  9.     out_of_stock() 

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

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

  1. count = fresh_fruit.get('banana', 0) 
  2. if count >= 2: 
  3.     pieces = slice_bananas(count
  4.     to_enjoy = make_smoothies(pieces) 
  5. else
  6.     count = fresh_fruit.get('apple', 0) 
  7.     if count >= 4: 
  8.         to_enjoy = make_cider(count
  9.     else
  10.         count = fresh_fruit.get('lemon', 0) 
  11.         if count
  12.             to_enjoy = make_lemonade(count
  13.         else
  14.             to_enjoy = 'Nothing' 

这种难看的写法其实在Python代码里特别常见。幸好现在有了海象操作符,让我们能够轻松地模拟出很接近switch/case的方案。

  1. if (count := fresh_fruit.get('banana', 0)) >= 2: 
  2.     pieces = slice_bananas(count
  3.     to_enjoy = make_smoothies(pieces) 
  4. elif (count := fresh_fruit.get('apple', 0)) >= 4: 
  5.     to_enjoy = make_cider(count
  6. elif count := fresh_fruit.get('lemon', 0): 
  7.     to_enjoy = make_lemonade(count
  8. else
  9.     to_enjoy = 'Nothing' 

这个版本只比原来短五行,但是看起来却清晰得多,因为嵌套深度与缩进层数都变少了。只要碰到刚才那种难看的结构,我们就应该考虑能不能改用海象操作符来写。

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

  1. FRUIT_TO_PICK = [ 
  2.     {'apple': 1, 'banana': 3}, 
  3.     {'lemon': 2, 'lime': 5}, 
  4.     {'orange': 3, 'melon': 2}, 
  5.  
  6. def pick_fruit(): 
  7.     if FRUIT_TO_PICK: 
  8.         return FRUIT_TO_PICK.pop(0) 
  9.     else
  10.         return [] 
  11.  
  12. def make_juice(fruit, count): 
  13.     return [(fruit, count)] 
  14.  
  15. bottles = [] 
  16. fresh_fruit = pick_fruit() 
  17. while fresh_fruit: 
  18.     for fruit, count in fresh_fruit.items(): 
  19.         batch = make_juice(fruit, count
  20.         bottles.extend(batch) 
  21.     fresh_fruit = pick_fruit() 
  22.  
  23. print(bottles) 

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

如果想复用这行代码,可以考虑loop-and-a-half 模式。这个模式虽然能消除重复,但是会让while循环看起来很笨,因为它成了无限循环,程序只能通过break语句跳出这个循环。

  1. FRUIT_TO_PICK = [ 
  2.     {'apple': 1, 'banana': 3}, 
  3.     {'lemon': 2, 'lime': 5}, 
  4.     {'orange': 3, 'melon': 2}, 
  5.  
  6. bottles = [] 
  7. while True:                     # Loop 
  8.     fresh_fruit = pick_fruit() 
  9.     if not fresh_fruit:         # And a half 
  10.         break 
  11.     for fruit, count in fresh_fruit.items(): 
  12.         batch = make_juice(fruit, count
  13.         bottles.extend(batch) 
  14.  
  15. print(bottles) 

有了海象操作符,就不需要使用loop-and-a-half模式了,我们可以在每轮循环的开头给fresh_fruit变量赋值,并根据变量的值来决定要不要继续循环。这个写法简单易读,所以应该成为首选方案。

  1. FRUIT_TO_PICK = [ 
  2.     {'apple': 1, 'banana': 3}, 
  3.     {'lemon': 2, 'lime': 5}, 
  4.     {'orange': 3, 'melon': 2}, 
  5.  
  6. bottles = [] 
  7. while fresh_fruit := pick_fruit(): 
  8.     for fruit, count in fresh_fruit.items(): 
  9.         batch = make_juice(fruit, count
  10.         bottles.extend(batch) 
  11.  
  12. print(bottles) 

在其他一些场合,赋值表达式也能缩减重复代码。总之,如果某个表达式或赋值操作多次出现在一组代码里面,那就可以考虑用赋值表达式把这段代码改得简单一些。

要点

 

  • 赋值表达式通过海象操作符(:=)给变量赋值,并且让这个值成为这条表达式的结果,于是,我们可以利用这项特性来缩减代码。
  • 如果赋值表达式是大表达式里的一部分,就得用一对括号把它括起来。
  • 虽说Python不支持switch/case与do/while结构,但可以利用赋值表达式清晰地模拟出这种逻辑。

原文地址:https://www.toutiao.com/i6946127157915632160/