说说我对[lambda x: x*i for i in range(4)]的理解
- 前言
这段代码早前在群里看到过,直接上图
第一眼看,不就是匿名函数吗?不就是列表推导式吗?装神弄鬼,答案简单。列表推导式里循环遍历4次,遍历结束return 出来的就是4个函数引用实例,并且i分别为 [0, 1, 2, 3 ],print中将对4个实例进行调用,那么答案就出来了 [0 * 2, 1 * 2, 2 * 2, 3 * 2] = [0, 2, 4, 6]。def multipliers(): return [lambda x:i*x for i in range(4)] print([m(2) for m in multipliers()])
- 为什么要发博文
这里我停顿一下,当然不是为了卖关子,既然大家冲浪来找这个问题,当然是自己在本地运行后对结果有所疑惑了。那我之所以想说说我的理解,是因为在网上看到太多对这个问题的说法了。有的网友说是 闭包 导致的,有的网友说是python的 延迟绑定 导致的,还有说 lambda函数签名 导致的等等等等。带着这些看法我们继续往下说 - 本地执行此函数
结果出乎意料,[6, 6, 6, 6]。我第一反应下的答案显然错了,想得太简单了 - 用公司小伙的话,我们OB一波
这是一个典型的列表推导式,简而言之就是在列表中推导计算并且将计算的结果放入列表,上面这串代码我们可以写成:
执行简化的这段代码def multipliers(): instance_list = [] for item in range(4): res = lambda x: item * x instance_list.append(res) return instance_list print([func(2) for func in multipliers()])
只是简化了代码,结果不变。那我们继续操作,将代码从函数里拿出来,看看结果
我们也看到了结果,依然是 [6, 6, 6, 6]。前面我们说过这可能是 闭包函数 的局部变量导致的,此处确实为闭包函数,因为当执行 lambda x:i*x 这串代码时调用了上一层函数multipliers()的局部命名空间的变量i,所以此处是闭包函数。但真的是这个原因么,显然不是。在去除外层函数multipliers后,已经失去了闭包函数的结构了,但是两次执行的结果相同。由此可知结论:不是闭包函数导致的,得出结论后继续往下看
继续简化代码,将lambda函数替换成普通函数,得出结果依然是 [6, 6, 6, 6]。由此,得到结论 不是lambda函数导致的 - 如此结果的原理是什么呢?
我们以最简化版的代码为例,加以说明
就像上面所说的:四次循环中外层函数命名空间中的 i 从 0–>1–>2–>3 最后固定为3,而在此过程中内嵌函数中因为没有定义 i 所以只有Lambda 函数动态运行时,在自己命名空间中找不到 i 才去外层函数复制 i = 3 过来,结果就是所有内嵌函数的 i 都为 3,导致得不到预计输出结果:[0,2,4,6] 只能得到 [3, 3, 3, 3]。
好了,去收拾了。没完全整完,还有涉及到的解决方法没说,还有 延迟绑定 没分析,哪天时间多,接着来