一段迷惑的使用海象操作符的代码
/ / / 阅读数:5839前言
分享一个在 Twitter 上看到的使用海象运算符的例子,觉得挺有意思的,代码是这样的:
>>> (a := 1) >>> (a, b := 2, 3) >>> print(f'a={a}, b={b}') a=1, b=2 |
里面这句(a, b := 2, 3)
特别有迷惑性,尤其是你写过 Go,很可能直觉的认为这个表达式没有问题。但是通过输出可以看到根本不符合预期,那么到底是哪里不对呢?我们慢慢的拆解一下
为什么要加圆括号
这个话题扩展起来很大,涉及到 Python 语法,我们逐步深入。
表达式和语句的区别
我们写的程序就是由一个或者多个语句组成的,语句 (Statement) 是一行或者多行代码,是整个程序的一个独立单元。而表达式 (Expression) 是一个特殊的语句,它只能包含标识符 (字母、数字、下划线等)、字面量 (Python 内置常量类型,如字符串、数字、浮点数) 和运算符(加减乘除、大于小于等于、异或、取余等):
1 + 2 # 表达式
a = 1 + 2 # 语句
如上面的例子,1 + 2
本质上是求值 (等于 3),a = 1 + 2
只表示代码逻辑,没有求值,只是个赋值语句
赋值表达式和赋值语句
我们在看一个例子:
In : x = 1
In : x
Out: 1
In : (y := 2)
Out: 2
In : y
Out: 2
其中x = 1
是一个赋值语句 (assignment statement),这样会将一个特定的值 (1) 设置到某个特定的存储地址去,这个位置被标记成一个特定的变量名称 (x)。而(y := 2)
是一个赋值表达式 (assignment expression),它比赋值语句多加了求值
这一步,也就是返回了结果 (2),所以可以看到Out: 2
这个输出。
而不加圆括号是语法错误:
In : y := 2
File "<ipython-input-8-b0043aac4290>", line 1
y := 2
^
SyntaxError: invalid syntax
这是因为在 Python 语言里,赋值表达式和赋值语句是不同的语法,下面 2 种方式是正确语法:
- 赋值表达式使用
:=
操作符 - 赋值语句使用
=
操作符
同样的,下面的代码也是语法错误:
In : (y = 2)
File "<ipython-input-13-a73f2c6719f5>", line 1
(y = 2)
^
SyntaxError: invalid syntax
所以不能直接想以:=
操作符的方式替代=
操作符,必须加一个括号来使用赋值表达式赋单个的值。
赋值语句转化成表达式的问题
刚才说赋值表达式和赋值语句语法不同,这么设计是因为将已经存在的赋值语句转化成表达式是容易出 Bug 的,这个在 C 语言里面就暴露的很明显,举个例子:
#include <stdio.h> int main() { int x = 3, y = 8; if (x = y) { printf("x and y are equal (x = %d, y = %d)", x, y); } return 0; } |
这是一段合规的代码,但是(x = y)
会让 x 被重新赋值为 y 的值造成这个判断为真,输出结果是x and y are equal (x = 8, y = 8)
。原因是代码中并没有用用于比较的操作符==
,这种错误很隐蔽,而 Python 或者 Go 等现在编程语言都是直接明确的抛出语法错误:
In : x = 1
...: y = 2
...: if x = y:
...: print('equal')
File "<ipython-input-5-5f6807f1b35f>", line 3
if x = y:
^
SyntaxError: invalid syntax
换用正确的海象操作符是可以达到这样的逻辑的 (虽然没必要):
In : x = 1
...: y = 2
...: if (x := y):
...: print(f'equal: {x=} {y=}')
...:
equal: x=2 y=2
为什么写了一句(a := 1)
答案也就在这里,这个画蛇添足的点就是这段混乱代码的问题所在。其实很简单,试试去掉它:
In : (a, b := 2, 3) --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-2-83c3cb14fd64> in <module> ----> 1 (a, b := 2, 3) NameError: name 'a' is not defined |
也就是说这句代码本身写的就不对。其实这个语句表示了一个元组,他有三个元素,分别是 a,b := 2
, 3。这里说 a 没有被定义,当然啦,之前就没赋值过 a。在 IPython 中执行,就能理解了:
In : (a := 1) # 相当于`a = 1`
Out: 1 # 赋值a=1,并且返回这个值(1)
In : (a, b := 2, 3)
Out: (1, 2, 3)
输出其实就是就是返回输入的数据的结果,所以(a, b := 2, 3)
返回值是一个元组,第一个元素 a 是前面赋值的结果,第二个元素是海象操作符求值的结果,第三个就是字面量 3。