PEP572: 海象运算符
/ / / 阅读数:9842前言
现在已经是 Python 3.8 的最后一个 alpha 版本,接着就是本月底要发布的的 3.8.0 beta 1 了。按规定,beta 之后就不会再添加 (修改) 功能了,之前非常有争议的 PEP 572 的实现已经算是很固定了,我们这篇文章就来先尝个鲜。看看这个新的赋值表达式
语法怎么用,何时用。
海象运算符
PEP572 的标题是「Assignment Expressions」,也就是「赋值表达式」,也叫做「命名表达式」,不过它现在被广泛的别名是「海象运算符」(The Walrus Operator)。因为:=
很像海象「眼睛小,长着两枚长长的牙」这个特点 ^_^。
语法和语义
我们不详细介绍 PEP 的内容,直接说应用场景。我觉得它主要可以用在 2 个地方
赋值给中间变量
这个小标题想了很久,没找到更合适能表达的。不过相信通过 2 个例子相信大家就能理解了。
首先是一个正则匹配的例子:
pattern = re.compile('s') data = 'ss' if pattern.match(data): print(pattern.match(data).group(0)) |
如果能匹配到条件,match 对象才会有 group 方法。但是这样写虽然节省到一行代码却让执行变慢了,因为重复地执行了 2 次re.match(data)
。正确的写法是:
match = pattern.match(data)
if match:
print(match.group(0))
代码也就只能写成这样了,但如果使用赋值表达式:
if (match := pattern.match(data)) is not None: print(match.group(0)) |
本来 if 这种控制结构语句只是求值表达式,看结果是不是符合条件。而在这里,它做了 3 件事:
- 对表达式
pattern.match (data)
求值 - 把值的结果赋值给 match
- 把 match 作为 if 的条件,判断它的值是不是 None
我对它的理解是:求值过程中也赋值了新的中间变量,这个 (些) 中间变量 (如这里的 match) 可以在代码块中被继续使用。
再看一个文件读取的例子:
while 1: line = fp.readline() if not line: break print(line) |
现在可以直接写成:
while (line := fp.readline()): print(line) |
这可以说是一种代码风格的改进了。
简化列表解析
列表解析性能好,而且非常 pythonic,但是它应用场景有限,我们看个例子:
results = [] for x in data: result = f(x) if result: results.append(result) |
这是一个日常开发里面比较常见的结构。现在是不能用列表解析的,不信的话下面的方案:
results = [ f(x) for x in data if f(x) ] |
这个是错误的,每次循环执行了 2 次 f 函数。现在用赋值表达式可以写成:
results = [ y for x in data if (y := f(x)) ] |
可以用列表解析了!
再看一个 PEP 提的例子:
stuff = [[y := f(x), x/y] for x in range(5)] |
其实又回到了赋值给中间变量
这个点,每一项包含了 y,以及要用 y 才能获得结果的 x/y。
上面说的就是海象运算符能实现的目的了~
Golang 里面的:=
:=
并不是 Python 首创的,Golang 里面有一个短变量声明
(Short variable declarations) 语法:
// ShortVarDecl = IdentifierList ":=" ExpressionList . i, j := 0, 10 f := func() int { return 7 } func f(n int) (res int, err error) { if _, err := f(n-1); err != nil { return } return } |
:=
的作用是替代 var 定义,声明时不需要指定类型。同时由于语言设计,和 Python 的赋值表达式一样,如上面的例子,f(n-1)
的第二个返回值 err 可以被后面的err != nil
使用,用来判断条件是否成立,我非常喜欢!
我对 PEP 572 的看法
在之前我曾经在知乎回答过「如何看待 PEP 572 ?」这个问题,当时我这么说:
这个 PEP 有明确的 Recommended use-cases, 在正确的地方使用,而不是滥用,当然不喜欢的可以不用,用旧的形式。我语言提供了更多特性和选择的机会,但控制权是开发者手里的,就像元类、描述符、dataclass 甚至装饰器等等都是有适用场景的。
有人觉得它不符合 Python 之禅,其实我个人感觉挺好的呀。现在 PEP 572 的实现已经合并到 Python3.8,试用下来非常赞。
Dustin Ingram 在 PyCON2019 上做了一个《PEP 572: The Walrus Operator》的分享,最后他也说自己不喜欢这个语法,但是他接着说:
You might say well i don't like it, that's totally fine. you don't have to like it if you don't like it then don't write it
我觉得说的非常好,没人强制你必须使用它~