Python 3.8中的Walrus操作符:一个入门指南

120 阅读3分钟

赋值表达式 (:=),或者说 "海象 "操作符,一直是最新版本的Python中引入的最受关注的特性。这个语言的新成员是在PEP 572 中提出的。在这篇文章中,我们看一下赋值表达式背后的原理,并通过各种例子了解如何使用它。

背景介绍

正如PEP中所说的,引入赋值表达式的主要理由是,程序员希望节省代码行数,甚至会重复一个子表达式(从而减慢程序的速度)来达到这个目的。

因此,他们不写:

match = re.match(data)
group = match.group(1) if match else None

他们写

group = re.match(data).group(1) if re.match(data) else None

表达式中的赋值使其更容易写出代码结构,在这种结构中,计算出的值会在以后作为某些条件的一部分被重新使用,并有助于使代码作者的意图更加明确。像C和Go等语言已经支持这样的功能。

在哪里使用Walrus操作符

让我们来看看在 Python 中如何使用 Walrus 操作符的几个例子。

例子 1: 当一个计算值被用作一个条件的一部分时,可以写成

match = pattern.search(data)
if match is not None:
    do_something(match)

可以写成:

if (match := pattern.search(data)) is not None:
  do_something(match)

上面这段代码可以理解为:如果match,就是数据的pattern.search,不是None,那么就对match做一些事情

例2:当读取一个用完的生成器资源时

chunk = resource.read(8192)
while chunk:
    process(chunk)
    chunk = resource.read(8192)

可以写得更短:

while chunk := resource.read(8192):
  process(chunk)

另一个例子,当读取一个文件中的所有行时:

line = f.readline()
while line:
    do_something(line)
    line = f.readline()

可以简洁地写成:

while line := f.readline():
    do_something(line)

例3:重复使用一个计算成本很高的值时

filtered_data = [
  f(x) for x in data
  if f(x) is not None
]

可以写成:

filtered_data = [
  y for x in data
  if (y := f(x)) is not None
]

这样可以节省重新计算f(x) ,可以提高程序的性能,同时还可以保持行数不变。

PEP的作者还通过展示Python标准库中使用海象操作符可以节省行数并使意图更明确的真实例子来合理化这一特性。

不使用海象操作符的地方

在某些情况下不允许使用赋值表达式,以避免产生歧义--比如在表达式语句的顶层使用,而不使用圆括号。这简化了用户在赋值语句 (=) 和赋值表达式 (:=) 之间的选择--不存在两者都有效的语法位置。

y := f(x)  # INVALID
(y := f(x))  # Valid, though not recommended

y0 = y1 := f(x)  # INVALID
y0 = (y1 := f(x))  # Valid, though discouraged


foo(x = y := f(x))  # INVALID
foo(x=(y := f(x)))  # Valid, though probably confusing

需要记住的事情

  • 赋值表达式引入新的作用域,而是受当前作用域的约束。如果当前的作用域包含目标的nonlocalglobal 声明,那么赋值表达式将尊重该声明。

  • 有一种特殊情况:在listsetdict 理解中或在生成器表达式中出现的赋值表达式将目标绑定在包含的作用域中,如果该作用域中存在对目标的nonlocalglobal 声明,那么它将遵守该声明。所以可以做这样的事情。

total = 0
partial_sums = [total := total + v for v in values]
print("Total:", total)

然而,赋值表达式的目标名称不能与出现在包含赋值表达式的任何理解中的for-目标名称相同。