背景
Python 的语法风格确实是懒人福音,真是做到了多写一行都是罪。就拿 Python-3.8 版本来说吧,为了少写一行代码,直接搞出了一个新的运算符 `:= `。
先给结论!这个运算符不是必要的。也就是说没有它我们也能写程序,只是有了它之后我们的代码可以更加简洁。
下面我就来说一下这个运算符的故事!
阶段 一
这个阶段大家做事都中规中矩,代码上直抒胸臆。给个例子吧。假设我们有一个函数,它是用来处理列表的,它会在处理之前检查一下列表的长度,当长度大于 7 时直接退出。阶段一这个时候的代码看起来如下。
代码语言:javascript复制def fun(lst=None):
"""
Parameters:
lst: 列表
Return:
None
"""
#
if len(lst) > 7:
print(f"len(lst) = {len(lst)} gt 7 , not supported .")
return
# 其它逻辑
这里有一个小小的问题,就是 `len(lst)` 在命中 if 的语句时候它还会在 print 语句里面再被计算一次。如果计算本身的开销就比较高,少计算一次就非常有吸引力了,阶段二就是向这个方向进化的。
阶段二
阶段二的写法也是非常直接,只要保存一下第一次计算的结果,第二次的时候直接取结果,这样就不用再计算一次了。上代码
代码语言:javascript复制def fun(lst=None):
"""
Parameters:
lst: 列表
Return:
None
"""
#
n = len(lst)
if n > 7:
print(f"len(lst) = {n} gt 7 , not supported .")
return
# 其它逻辑
阶段二的写法也不是完全没有可以改进的地方,我们看 n 其实只在 if 块里面有用到,但是它的声明位置是在 if 之外的。这个就给人一种,这个 n 非常重要后面的代码还会用到它的感觉。也就是说这种写法没有办法表现出 n 就是一个临时变量。
好在这种语义已经可以在 Python-3.8 这个版本中表达了,不过我们要借助全新的运算符 `:=` 来实现。 详细的请看阶段三。
阶段三
阶段三是真正的做到了形与意合,并且没有什么学习成本,语法上可以说是一看就懂,我直接上代码。
代码语言:javascript复制def fun(lst=None):
"""
Parameters:
lst: 列表
Return:
None
"""
#
if (n:= len(lst)) > 7:
print(f"len(lst) = {n} gt 7 , not supported .")
return
# 其它逻辑
现在从词法上看,就能非常明确地知道 n 只在 if 语句内起作用。由于阶段三只是阶段二的语法糖,也就是说从作用域上来讲 n 在 if 语句之后还是可以正常访问,这个应该就是唯一美中不足的地方了吧。
为什么说 := 是语法糖
这一点是从官方文档上不能直接看出来的,需要我们去看 fun 函数两种不同写法,编译出来的字节码。
代码语言:javascript复制In [1]: import dis
In [2]: def fun(lst=None):
...: """
...: Parameters:
...: lst: 列表
...:
...: Return:
...: None
...: """
...: #
...:
...: if (n:= len(lst)) > 7:
...: print(f"len(lst) = {n} gt 7 , not supported .")
...: return
...:
In [3]: dis.dis(fun)
1 0 RESUME 0
11 2 LOAD_GLOBAL 1 (NULL len)
14 LOAD_FAST 0 (lst)
16 PRECALL 1
20 CALL 1
30 COPY 1
32 STORE_FAST 1 (n)
34 LOAD_CONST 1 (7)
36 COMPARE_OP 4 (>)
42 POP_JUMP_FORWARD_IF_FALSE 21 (to 86)
12 44 LOAD_GLOBAL 3 (NULL print)
56 LOAD_CONST 2 ('len(lst) = ')
58 LOAD_FAST 1 (n)
60 FORMAT_VALUE 0
62 LOAD_CONST 3 (' gt 7 , not supported .')
64 BUILD_STRING 3
66 PRECALL 1
70 CALL 1
80 POP_TOP
13 82 LOAD_CONST 4 (None)
84 RETURN_VALUE
11 >> 86 LOAD_CONST 4 (None)
88 RETURN_VALUE
In [4]: def fun(lst=None):
...: """
...: Parameters:
...: lst: 列表
...:
...: Return:
...: None
...: """
...: #
...: n = len(lst)
...: if n > 7:
...: print(f"len(lst) = {n} gt 7 , not supported .")
...: return
...:
In [5]: dis.dis(fun)
1 0 RESUME 0
10 2 LOAD_GLOBAL 1 (NULL len)
14 LOAD_FAST 0 (lst)
16 PRECALL 1
20 CALL 1
30 STORE_FAST 1 (n)
11 32 LOAD_FAST 1 (n)
34 LOAD_CONST 1 (7)
36 COMPARE_OP 4 (>)
42 POP_JUMP_FORWARD_IF_FALSE 21 (to 86)
12 44 LOAD_GLOBAL 3 (NULL print)
56 LOAD_CONST 2 ('len(lst) = ')
58 LOAD_FAST 1 (n)
60 FORMAT_VALUE 0
62 LOAD_CONST 3 (' gt 7 , not supported .')
64 BUILD_STRING 3
66 PRECALL 1
70 CALL 1
80 POP_TOP
13 82 LOAD_CONST 4 (None)
84 RETURN_VALUE
11 >> 86 LOAD_CONST 4 (None)
88 RETURN_VALUE
可以看到阶段三和阶段二的字节码是一模一样的。
最后
都到这里了,是时候图穷匕见了!