Python_Tips[2] -> 函数延后估值及字节码分析
函数延后估值及字节码分析
在一个循环中定义了函数 f 但是并未对其进行调用,在循环结束后调用,此时i值为3故最终3个函数输出均为9。而非1, 4, 9。
这是由于在定义闭包函数 f 时,传入变量 i,而在循环结束后才调用函数,此时的 i 已为 3,下面使用字节码来查看并论证这一运行顺序。
1 import dis 2 3 def count(): 4 fs = [] 5 for i in range(1,4): 6 def f(): 7 return i*i 8 fs.append(f) 9 return fs 10 11 def run(): 12 f1, f2, f3 = count() 13 # When the function called, the value of i is 3 14 print(f1()) 15 print(f2()) 16 print(f3()) 17 18 # dis.dis(count) 19 run()
使用 dis对count函数的字节码进行查看,得到解释器运行字节码如下
1 5 0 BUILD_LIST 0 2 3 STORE_FAST 0 (fs) 3 4 6 6 SETUP_LOOP 54 (to 63) 5 9 LOAD_GLOBAL 0 (range) 6 12 LOAD_CONST 1 (1) 7 15 LOAD_CONST 2 (4) 8 18 CALL_FUNCTION 2 (2 positional, 0 keyword pair) 9 21 GET_ITER 10 >> 22 FOR_ITER 37 (to 62) 11 25 STORE_DEREF 0 (i) 12 13 7 28 LOAD_CLOSURE 0 (i) 14 31 BUILD_TUPLE 1 15 34 LOAD_CONST 3 (<code object f at 0x0000000000547AE0, file "C:/Users/XXXXX/Documents/Python Note/10_Python_Tips/10.4_Method_Call/Method_Call.py", line 7>) 16 37 LOAD_CONST 4 ('count.<locals>.f') 17 40 MAKE_CLOSURE 0 18 43 STORE_FAST 1 (f) 19 20 9 46 LOAD_FAST 0 (fs) 21 49 LOAD_ATTR 1 (append) 22 52 LOAD_FAST 1 (f) 23 55 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 24 58 POP_TOP 25 59 JUMP_ABSOLUTE 22 26 >> 62 POP_BLOCK 27 28 10 >> 63 LOAD_FAST 0 (fs) 29 66 RETURN_VALUE
通过字节码可以看到,
第 10 行进入迭代,
第 11 行中的 STORE_DEREF 会更新变量 i 的值,
第 13 行开始,进入到函数 f 定义的部分,而第 13 行则是关键,这里载入 Enclosure 即闭包上层的局部变量 i,而不是当前 i 的值,随后完成整个闭包函数 f 并返回。
因此,在 3 个函数 f 中,所存储的均为变量 i,所以在调用时结果自然相同。
相关阅读
1. 闭包函数