做者:Brett Slatkinpython
翻译:老齐程序员
与本文内容有关的图书:《跟老齐学Python:轻松入门》、《Python大学实用教程》express
Python3.8引入了一种叫作海象运算符(walrus)的新语法(译者注: 对于walrus的翻译,目前还没有出现对于Python的专门术语翻译,因此,此处姑且用字面意思“海象”),它实际上是一种赋值语句,用于解决Python语言中长期存在的、可能致使代码重复的问题。正常的赋值语句是a=b
,读做“a等于b”,而海象赋值语句是a:=b
,读做“a walrus /ˈwɔːlrəs/ b”(由于:=
看起来像一对眼球和獠牙,相似于海象。注意:此语句尚未适合的中文读法,总不能读做“a海象b”吧)。bash
海象运算符的优点在于能在不容许赋值的地方(如if语句的条件表达式中)使用赋值变量。海象运算符左侧有个标识符,赋值表达式的值等于分配给这个标识符的值。微信
例如,假设我有一篮子新鲜水果,我正试图经营一家果汁店。在这里,我定义了篮子里的东西:markdown
fresh_fruit = { 'apple': 10, 'banana': 8, 'lemon': 5, } 复制代码
当顾客到柜台点柠檬水时,我须要确保篮子里至少有一个柠檬用来榨果汁。个人操做方法是检索柠檬的数量,而后使用if语句查询非零的值:app
def make_lemonade(count): ... def out_of_stock(): ... count = fresh_fruit.get('lemon', 0) if count: make_lemonade(count) else: out_of_stock() 复制代码
这个看似简单的代码问题吸引了过多的关注。count
变量仅在if语句的第一个代码块中使用,在if语句上方定义count
会使它看起来比实际状况更为重要,好像后面的全部代码(包括else块)都须要访问count
变量,然而事实并不是如此。ide
咱们获取一个值,检查它是否为非零,而后使用它。这种模式在Python中很是常见。许多程序员试图绕过屡次出现count
的状况,甚至不惜使用各类损害可读性的招数。如今好了,在Python3.8中增长了海象运算符,能够简化上面的代码。函数
if count := fresh_fruit.get('lemon', 0): make_lemonade(count) else: out_of_stock() 复制代码
虽然如今只是少了一行,但可读性提升不少。由于如今能够清楚地看到count
只与if语句的第一行相关。赋值表达式首先为count
变量赋值,而后在if语句的上下文中使用该值,以肯定如何继续控制流程。这两步行为——分配和评估——是海象运算符的基本性质。oop
柠檬是很是有效的,因此个人柠檬水配方中只须要一个,这意味着非零检查就足够了。不过,若是顾客点了苹果酒,我须要确保至少有四个苹果。在这里,我从fruit_basket
字典中获取计数,而后在if语句中使用比较表达式:
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进行比较。在柠檬水的例子中不须要使用圆括号,由于赋值表达式自己就是一个非零检查;它不是一个较大表达式的子表达式。与其余表达式同样,应尽可能避免使用圆括号把赋值表达式括起来。
有时候会出现一种相似的重复模式,那就是当我须要根据某些条件在封闭范围内分配一个变量,而后在函数中的稍后位置引用该变量。例如,假设客户订购了一些香蕉冰沙。为了制做它们,我须要至少两个香蕉的香蕉片,不然将引起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() 复制代码
另外一种常见的方法是将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() 复制代码
第二种方法可能会让人以为奇怪,由于这意味着pieces
变量出如今了条件语句中两个不一样的位置,能够在这两个位置进行初始定义。因为Python的做用域规则,这种分割定义在技术上是可行的,但它的可读性很差,也不优雅。这就是许多人喜欢上面那种结构的缘由,在它里面的pieces = 0
的赋值在前面出现。
海象运算符也能够用一行代码来缩短这个例子。这个小变化消除了对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() 复制代码
使用海象运算符还能够提升在条件语句中分别在两个分支中的pieces
复制的可读性。当count
定义再也不位于if语句以前时,跟踪pieces
变量变得更容易:
if (count := fresh_fruit.get('banana', 0)) >= 2: pieces = slice_bananas(count) else: pieces = 0 try: smoothies = make_smoothies(pieces) except OutOfBananas: out_of_stock() 复制代码
初学Python的程序员常常遇到的一个难题是缺乏灵活的switch/case
语句,与此类功能近似的通常作法是使用多个if、elif和else语句的深度嵌套。
例如,假设我想实现一个优先级系统,这样每一个客户均可以自动得到最好的果汁,而没必要预约。在这里,我设置这样的流程,让它先供应香蕉冰沙,而后供应苹果酒,最后供应柠檬水:
count = fresh_fruit.get('banana', 0) if count >= 2: pieces = slice_bananas(count) to_enjoy = make_smoothies(pieces) 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_lemonade(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_lemonade(count) else: to_enjoy = 'Nothing' 复制代码
使用海象运算符版本只比原来的版本短五行,可是因为嵌套和缩进的减小,可读性有了很大提升。若是在你的代码中看到像上面那样丑陋的代买,我建议你尽可能使用海象运算符重写。
初学Python的程序员经常遇到的另外一个挫折是缺乏do/while循环构造。例如,假设我想在新水果到货时将果汁装入瓶中,直到没有剩余的水果为止。在这里,我用while循环实现这个逻辑:
def pick_fruit(): ... def make_juice(fruit, count): ... 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()
,一个在循环前设置初始条件,另外一个在循环结束时补充到货的水果列表。
在这种状况下,改进代码复用的策略是使用loop-and-a-half(若是出现这种状况,须要当即退出并跳过循环体中的任何剩余语句)。这消除了多余的行,但它也破坏了while循环,使其成为一个愚蠢的无限循环。如今,循环的全部流控制都依赖于break条件语句:
bottles = [] while True: # Loop fresh_fruit = pick_fruit() if not fresh_fruit: # And a half break for fruit, count in fresh_fruit.items(): batch = make_juice(fruit, count) bottles.extend(batch) 复制代码
海象运算符消除了对loop-and-a-half的须要。方法是:容许从新设置fresh_fruit
变量,而后每次都经过while循环有条件地求值。此解决方案简短易读,应该是代码中的首选方法:
bottles = [] while fresh_fruit := pick_fruit(): for fruit, count in fresh_fruit.items(): batch = make_juice(fruit, count) bottles.extend(batch) 复制代码
在许多其余状况下,可使用还有海象运算符的赋值表达式来消除冗余。一般,当你发现本身在许多行中屡次重复同一个表达式或赋值时,应该考虑使用海象运算符来提升可读性。
原文连接:effectivepython.com/2020/02/02/…
关注微信公众号:老齐教室。读深度文章,得精湛技艺,享绚丽人生。