我们想生成一组底数不同的指数函数。
Edition 1
def makeFunc():
return [lambda x: base**x for base in range(5)]
acts = makeFunc()
[act(2) for act in acts]
>> [16, 16, 16, 16, 16]
base都是4,为什么?我推测,出于效率考虑,python函数并不在定义时“解引用”任何变量。也就是说,将base替换为0, 1...4的操作并没有在定义时发生。而在运行时,base已然为4。
下面是makeFunc的字节码,上述行为的线索就在其中。
2 0 LOAD_CONST 1 ( at 0x0000018B42A70300, line 2>)
2 LOAD_CONST 2 ('makeFunc..')
4 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 3 (5)
10 CALL_FUNCTION 1
12 GET_ITER
14 CALL_FUNCTION 1
16 RETURN_VALUE
Disassembly of at 0x0000018B42A70300, line 2>:
2 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 16 (to 22)
6 STORE_DEREF 0 (base)
8 LOAD_CLOSURE 0 (base)
10 BUILD_TUPLE 1
12 LOAD_CONST 0 ( at 0x0000018B42A70270, line 2>)
14 LOAD_CONST 1 ('makeFunc...')
16 MAKE_FUNCTION 8
18 LIST_APPEND 2
20 JUMP_ABSOLUTE 4
>> 22 RETURN_VALUE
Disassembly of at 0x0000018B42A70270, line 2>:
2 0 LOAD_DEREF 0 (base)
2 LOAD_FAST 0 (x)
4 BINARY_POWER
6 RETURN_VALUE
注意观察第三段字节码,即>部分。这段字节码显示出lambda函数的运行过程:
- 解引用外部变量(base)
- 加载本地变量(x)
- 二元幂运算
- 返回。
这说明,lambda函数被调用时,名称base的解引用才会发生。如果base在函数定义时就被替换,这里的字节码应该是加载常数才对。
以上讨论让我们对python变量解引用的时机有了如下认识:
python变量在执行时解引用
Edition 2
def makeAction(base):
return lambda x: base**x
acts = [makeAction(i) for i in range(5)]
[act(2) for act in acts]
>> [0, 1, 4, 9, 16]
为每个lambda制作一个独立的闭包,顺利做出了5个不一样的函数。