多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
## 问题来源   最近看到了一个python程序题,就三行代码,却思考了很久才考虑明白,决定分享一下。 ~~~ def num(): return [lambda x:i*x for i in range(4)] print([m(2) for m in num()]) ~~~ 预计结果为:0, 2, 4, 6 实际输出为:6, 6, 6, 6 ## 思路分析 其实把上面的代码拆分一下,等价于下面的代码 ~~~ def func(): fun_lambda_list = [] for i in range(4): def lamb(x): return x*i fun_lambda_list.append(lamb) return fun_lambda_list ~~~ 我们再把上面的代码加两行print输出,让结果看的更加明显: PS:**locals()** 函数会以字典类型返回当前位置的全部局部变量。 ~~~ def func(): fun_lambda_list = [] for i in range(4): def lamb(x): print('Lambda函数中 i {} 命名空间为:{}:'.format(i, locals())) return x*i fun_lambda_list.append(lamb) print('外层函数 I 为:{} 命名空间为:{}'.format(i, locals())) return fun_lambda_list fl = func() fl[0](1) fl[1](1) fl[2](1) fl[3](1) ~~~ 我们会发现,打印的结果为: ~~~ 外层函数 I 为:0 命名空间为:{'lambda_': <function func.<locals>.lambda_ at 0x00000116B6837488>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x00000116B6837488>], 'i': 0} 外层函数 I 为:1 命名空间为:{'lambda_': <function func.<locals>.lambda_ at 0x00000116B6837510>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x00000116B6837488>, <function func.<locals>.lambda_ at 0x00000116B6837510>], 'i': 1} 外层函数 I 为:2 命名空间为:{'lambda_': <function func.<locals>.lambda_ at 0x00000116B6837598>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x00000116B6837488>, <function func.<locals>.lambda_ at 0x00000116B6837510>, <function func.<locals>.lambda_ at 0x00000116B6837598>], 'i': 2} 外层函数 I 为:3 命名空间为:{'lambda_': <function func.<locals>.lambda_ at 0x00000116B6837620>, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x00000116B6837488>, <function func.<locals>.lambda_ at 0x00000116B6837510>, <function func.<locals>.lambda_ at 0x00000116B6837598>, <function func.<locals>.lambda_ at 0x00000116B6837620>], 'i': 3} Lambda函数中 i 3 命名空间为:{'x': 1, 'i': 3}: Lambda函数中 i 3 命名空间为:{'x': 1, 'i': 3}: Lambda函数中 i 3 命名空间为:{'x': 1, 'i': 3}: Lambda函数中 i 3 命名空间为:{'x': 1, 'i': 3}: ~~~   可以发现:四次循环中外层函数命名空间中的** i 从**** 0-->1-->2-->3 最后固定为****3**,而在此过程中内嵌函数lamb函数中因为没有定义 i 所以只有lamb函数动态运行时,在自己命名空间中找不到 i 才去外层函数复制 i = 3 过来,结果就是所有lamb函数的 i 都为 3,导致得不到预计输出结果:0,2,4,6 只能得到 6,6,6,6。 ## 解决办法 变闭包作用域为局部作用域 ``` def func(): fun_lambda_list = [] for i in range(4): def lambda_(x,i=i): print('Lambda函数中 i {} 命名空间为:{}:'.format(i, locals())) return x*i fun_lambda_list.append(lambda_) print('外层函数 I 为:{} 命名空间为:{}'.format(i, locals())) return fun_lambda_list fl = func() fl[0](1) fl[1](1) fl[2](1) fl[3](1) ``` ``` 外层函数 I 为:0 命名空间为:{'lambda_': <function func.<locals>.lambda_ at 0x0000021F12227488>, 'i': 0, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x0000021F12227488>]} 外层函数 I 为:1 命名空间为:{'lambda_': <function func.<locals>.lambda_ at 0x0000021F12227510>, 'i': 1, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x0000021F12227488>, <function func.<locals>.lambda_ at 0x0000021F12227510>]} 外层函数 I 为:2 命名空间为:{'lambda_': <function func.<locals>.lambda_ at 0x0000021F12227598>, 'i': 2, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x0000021F12227488>, <function func.<locals>.lambda_ at 0x0000021F12227510>, <function func.<locals>.lambda_ at 0x0000021F12227598>]} 外层函数 I 为:3 命名空间为:{'lambda_': <function func.<locals>.lambda_ at 0x0000021F12227620>, 'i': 3, 'fun_lambda_list': [<function func.<locals>.lambda_ at 0x0000021F12227488>, <function func.<locals>.lambda_ at 0x0000021F12227510>, <function func.<locals>.lambda_ at 0x0000021F12227598>, <function func.<locals>.lambda_ at 0x0000021F12227620>]} Lambda函数中 i 0 命名空间为:{'i': 0, 'x': 1}: Lambda函数中 i 1 命名空间为:{'i': 1, 'x': 1}: Lambda函数中 i 2 命名空间为:{'i': 2, 'x': 1}: Lambda函数中 i 3 命名空间为:{'i': 3, 'x': 1}: 输出结果 ```   所以,再回到最开始的那段代码。`lambda x: x*i` 为内层(嵌)函数,他的命名空间中只有 {'x': 1} 没有 i ,所以运行时会向外层函数(这儿是列表解析式函数 \[ \])的命名空间中请求 i 。而当列表解析式运行时,列表解析式命名空间中的 i 经过循环依次变化为 0-->1-->2-->3 最后固定为 3 ,所以当 `lambda x: x*i` 内层函数运行时,去外层函数取 i 每次都只能取到 3。   将代码改成下面这样输出就会变成0,2,4,6. ~~~ def num(): return [lambda x,i=i:i*x for i in range(4)] print([m(2) for m in num()]) ~~~ ## LEGB规则   只有函数、类、模块会产生作用域,代码块不会产生作用域。作用域按照变量的定义位置可以划分为4类: ~~~ Local(函数内部)局部作用域 Enclosing(嵌套函数的外层函数内部)嵌套作用域(闭包) Global(模块全局)全局作用域 Built-in(内建)内建作用域 ~~~   python解释器查找变量时,会按照顺序依次查找**局部作用域--->嵌套作用域--->全局作用域--->内建作用域**,在任意一个作用域中找到变量则停止查找,所有作用域查找完成没有找到对应的变量,则抛出 NameError: name 'xxxx' is not defined的异常。