关于一道面试题的思考
def testFun(): return [lambda x:i*x for i in range(4)] for func in testFun(): print(func(2))
输出:
6 6 6 6
最开始百思不得其解。然后恰巧在知乎上看到这个例子。
def testFun1(): l = [] for i in range(4): def foo(x): return x*i l.append(foo) return l
这里面需要注意以下几点:
1 testFun 与 testFun1 完全等价。这一点一会证明。
2 函数 foo 是一个闭包函数。这一点很重要。
def testFun(): return [lambda x:i*x for i in range(4)] for func in testFun(): print(func.__closure__,func(2)) print('='*80) def testFun1(): l = [] for i in range(4): def foo(x): return x*i l.append(foo) return l for func in testFun1(): print(func.__closure__)
输出:
(<cell at 0x0000025CC38C76D8: int object at 0x0000000075F3C6F0>,) 6 (<cell at 0x0000025CC38C76D8: int object at 0x0000000075F3C6F0>,) 6 (<cell at 0x0000025CC38C76D8: int object at 0x0000000075F3C6F0>,) 6 (<cell at 0x0000025CC38C76D8: int object at 0x0000000075F3C6F0>,) 6 ================================================================================ (<cell at 0x0000025CC38C7BE8: int object at 0x0000000075F3C6F0>,) (<cell at 0x0000025CC38C7BE8: int object at 0x0000000075F3C6F0>,) (<cell at 0x0000025CC38C7BE8: int object at 0x0000000075F3C6F0>,) (<cell at 0x0000025CC38C7BE8: int object at 0x0000000075F3C6F0>,)
你会发现,testFun 与 testFun1 的返回值完全一样,同时返回的列表的每个元素都是闭包。每个闭包内的自由变量都是指向同一个自由变量。
为什么呢?为什么是这样?我当初也是这样问自己。返回的列表内是闭包不难理解,但是为什么每个闭包的自由变量都是一样的呢?
然后,我对上面代码加了验证。如下
def testFun1(): l = [] for i in range(4): print('flag1',i) def foo(x): print('flag2',i) return x*i l.append(foo) return l for func in testFun1(): print(func.__closure__)
输出:
flag1 0 flag1 1 flag1 2 flag1 3 (<cell at 0x00000178EA0876D8: int object at 0x0000000075F3C6F0>,) (<cell at 0x00000178EA0876D8: int object at 0x0000000075F3C6F0>,) (<cell at 0x00000178EA0876D8: int object at 0x0000000075F3C6F0>,) (<cell at 0x00000178EA0876D8: int object at 0x0000000075F3C6F0>,)
看到这个输出结果,我感觉到恍然大悟,茅塞对开。
testFun1 函数执行时,内部的 foo 函数,实际上只是处于定义阶段。函数在定义阶段值检测语法错误,不执行代码。
如果你很难理解,看下面的代码就明白了。
def foo(): print('from foo') bar() def bar(): print('from bar') foo()
看到这段代码,你可能觉得这段代码会报错。然而它会正常运行。输出结果如下:
from foo from bar
因为定义foo函数时,它并不会实际执行代码。如果执行,这段代码肯定会出错。所以,它只是会检测语法有没有错误而已。
回归到上面。输出结果都是flag1,没有flag2,同样说明了testFun1函数内部定义的foo函数并没有执行,而仅仅是定义了而已。需要注意的是,这个foo是一个闭包函数,而闭包函数会对自由变量进行引用。
part 2:
那么这段代码做了什么呢?
for func in testFun1(): print(func(2))
变量func是闭包函数,也就是函数 foo 。func(2) 这是对闭包函数的调用。实际上这才处于闭包函数的调用阶段。
foo 内有一个对自由变量的引用。 当处于这里的调用阶段是,自由变量已经完成for循环。即 i 已经是 3 了。
所以,这段代码内的 func,每一个func 都相当于下面wrapper函数的返回值,即闭包函数foo。
def wrapper():
i = 3
def foo(x):
return x * i
return foo
所以,func(2)的返回值就是 6。面试题的答案自然就是 [6,6,6,6]了。
这就是我对这个面试题的理解。我觉的是没有问题的。如有不妥之处,还望指教。