Python中的变量作用域,LEGB规则和闭包原理
问题来源
最近看到了一个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() 函数会以字典类型返回当前位置的全部局部变量。
'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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
。
解决办法
变闭包作用域为局部作用域
'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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.
'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:778463939
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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
的异常。
本文来自博客园,作者:I'm_江河湖海,转载请注明原文链接:https://www.cnblogs.com/jhhh/p/16761072.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构