Python 关于后期绑定的问题,我开始怀疑人生了,求大神解析

2018-08-23 21:02:03 +08:00
 Alerta

当我直接 return 一个匿名函数的时候返回 0,2,4,6

def testFun():
    return(lambda x : i*x for i in range(4))
for everyLambda in testFun():
    print(everyLambda(2))
& python test.py
0
2
4
6

但是当我把匿名函数作为一个 temp 来返回的时候,结果却是 6,6,6,6

def testFun():
    temp = [lambda x : i*x for i in range(4)]
    return temp
for everyLambda in testFun():
    print(everyLambda(2))
& python test.py
6
6
6
6

求大神解释解释,这是为什么呀,急求明天就要面试了好紧张

2736 次点击
所在节点    Python
14 条回复
XiaoxiaoPu
2018-08-23 21:13:55 +08:00
两段代码的区别不是“把匿名函数作为一个 temp 来返回”,而是第一段代码返回的是生成器,在 for 循环的时候 i 才实际增加,第二段代码返回的是数组,在 for 循环开始的时候 i 已经增加到 3 了
newmind
2018-08-23 21:27:42 +08:00
第一个是括号(), 为生成器, 返回 generator
第二个是中括号[], 为列表生成式, 返回数组
HelloAmadeus
2018-08-23 21:34:24 +08:00
```
tmp = [lambda x: x*i for i for range(4)]
```
返回的是:
```
tmp = [lambda x: x*3, ...]
```
```
tmp = (lambda x: x*i for i for range(4))
返回的是:
```
tmp = (lambda x: x*0, lambda x: x**1, ...) # 这是一个生成器
```

列表表达式是即时计算的, 而生成器是迭代时才会计算. 返回的 lamda 匿名函数在查找 i 变量的时候, 列表表达式已经算计完了, 此时的 i 值为 3, 所以计算的返回结果是 6, 而在生成器计算的时候, `for i in range(4)` 的计算是惰性的, 只有你去迭代生成器的时候, i 的值才会 +1. 所以 lambda 表达式查找的 i 变量是 0, 1, 2, 3 的序列.
lixm
2018-08-23 22:23:46 +08:00
```python
from dis import dis

f1 = next((lambda x : i*x for i in range(4)))
dis(f1)
f2 = [lambda x : i*x for i in range(4)][0]
dis(f2)
```
都取第 0 个函数出来, 反编译一下就知道, 这两个唯一的区别就是 i,

f1 是 LOAD_DEREF i 作为闭包传入
f2 是 LOAD_GLOBAL i 作为全局变量传入

```python
print(f1.func_closure[0].cell_contents)
```
可以看出 作为闭包传入的 i 的值为 0

```python
print(f2.func_globals['i'])
```
可以看出 作为全局变量传入的 i 的值为 3

为什么会这样呢?因为 Python 里,一切都是引用, 在第一个例子里, 因为生成器是惰性求值, 你可以理解在执行 next, 或者 for 循环的时候, 才求出 i 的值, 所以 i 的值从 0 递增到 3。 在第二个例子里, 列表并不是惰性求值,i 作为一个引用, 值已经变为 3 了

可能说的不是很清楚, 抱歉
lolizeppelin
2018-08-23 22:27:23 +08:00
这不是 python 的问题。 我所知的语言里只有 erlang 不出这个问题
js 之类的一鸟样
cyrbuzz
2018-08-24 08:54:02 +08:00
我比较好奇有什么情况下会用这样的写法。
ipwx
2018-08-24 10:03:43 +08:00
你需要这么写

https://ideone.com/mrZkgF
whoami9894
2018-08-24 23:12:17 +08:00
补充一下,python 中只有 def,class,lambda 会产生作用域,所以这里 for 循环结束后 i 依然存在为 4,这里方括号列表解析生成的 lambda 可以看作是宏,所以会得到 6666 的结果
josephshen
2018-08-26 17:55:53 +08:00
我告诉你上面的解释是出现了显现给的解释,原因根源是 python 这门语言的设计问题,导致了分不清申明和赋值
josephshen
2018-08-26 17:57:09 +08:00
显示->现象
Alerta
2018-08-26 23:02:45 +08:00
@HelloAmadeus 感谢感谢 一下子明白了
Alerta
2018-08-26 23:05:03 +08:00
@lixm 感激,虽然一开始不是很懂 但是很欣赏你的思考方式,从最根源的现象去推导原因,学习了
Alerta
2018-08-26 23:07:41 +08:00
@cyrbuzz 一个是为了应对面试,二来只是自己无聊刷刷奇怪的题目而已。。。
U87
2018-08-27 16:34:47 +08:00
@lixm 真的 6, 反向思考...赞一下

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/482659

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX