一段迷惑的使用海象操作符的代码 解析

519 阅读4分钟

前言
分享一个在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的值造成这个判断为真,输出结果是 xandy 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。

以上就是本次分享的所有内容,想要了解更多欢迎前往公众号:Python 编程学习圈,每日干货分享