zoukankan      html  css  js  c++  java
  • fun = [lambda x: x*i for i in range(4)] 本质解析/原理,LEGB规则 闭包原理

    原文链接:https://www.cnblogs.com/shiqi17/p/9608195.html

    一、问题描述

    fun = [lambda x: x*i for i in range(4)]
    for item in fun:
        print(item(1))

    上述式子的输出结果:
    预计结果为:0, 2, 4, 6
    实际输出为:3, 3, 3, 3

    • 原理:i 在外层作用域。

     lambda x: x*i  为内层(嵌)函数,他的命名空间中只有 {'x': 1} 没有 i ,

    所以运行时会向外层函数(这儿是列表解析式函数 [ ])的命名空间中请求 i而当列表解析式运行时,

    列表解析式命名空间中的 i 经过循环依次变化为 0-->1-->2-->3 最后固定为 3 ,

    所以当  lambda x: x*i  内层函数运行时,去外层函数取 i 每次都只能取到 3

    • 解决办法:变闭包作用域为局部作用域。

    给内层函数  lambda x: x*i  增加参数,命名空间中有了用来存储每次的 i ,
    即改成  [lambda x, i=i: x*i for i in range(4)]  这样每一次,内部循环生成一个lambda 函数时,
    都会把 --i--作为默认参数传入lambda的命名空间
    循环4次实际lambda表达式为:
    第一次:lambda x, i=0 第二次:lambda x, i=1 第三次:lambda x, i=2 第四次:lambda x, i=3

    fun = [lambda x, i=i: x*i for i in range(4)]
    for item in fun:
        print(item(1))
    
    #输出结果为:
    0
    1
    2
    3

     二、上面看不懂就看这儿 

    函数 fun = [lambda x: x*i for i in range(4)] 等价于:如下函数

    def func():
        fun_lambda_list = []
    
        for i in range(4):
            def lambda_(x):
                return x*i
            fun_lambda_list.append(lambda_)
            
        return fun_lambda_list

    查看该函数命名空间及 I 值变化:

    def func():
        fun_lambda_list = []
        for i in range(4):
    
            def lambda_(x):
                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)

    #运行结果为:为了排版美观,我已将输出lambda_函数地址改名为:lam函数1 2 3

    外层函数I为:0 命名空间为:{'i': 0, 'lambda_': lam函数1 'fun_lambda_list': [lam函数1]}
    外I:1 命空:{'i': 1, 'lambda_': lam函数2, 'fun_lambda_list': [lam函数1, lam函数2]}
    外I:2 命空:{'i': 2, 'lambda_': lam函数3, 'fun_lambda_list': [lam函数1, lam函数2, lam函数3]}
    外I:3 命空:{'i': 3, 'lambda_': lam函数4, 'fun_lambda_list': [lam函数1, lam函数2, lam函数3, lam函数4]}
    Lambda函数中 i 3 命名空间为:{'i': 3, 'x': 1}:
    Lambda函数中 i 3 命名空间为:{'i': 3, 'x': 1}:
    Lambda函数中 i 3 命名空间为:{'i': 3, 'x': 1}:
    Lambda函数中 i 3 命名空间为:{'i': 3, 'x': 1}:
    

    可以看见:就像上面所说的:四次循环中外层函数命名空间中的 i 从 0-->1-->2-->3 最后固定为3,
    而在此过程中内嵌函数-Lambda函数中因为没有定义 i 所以只有Lambda 函数动态运行时,
    在自己命名空间中找不到 i 才去外层函数复制 i = 3 过来,结果就是所有lambda函数的 i 都为 3,
    导致得不到预计输出结果:0,1,2,3 只能得到 3, 3, 3, 3

    • 解决办法:变闭包作用域为局部作用域。
    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_)
        return fun_lambda_list
    
    fl = func()
    res = []
    
    res.append(fl[0](1))
    res.append(fl[1](1))
    res.append(fl[2](1))
    res.append(fl[3](1))
    
    print(res)
    
    #输出结果为:
    Lambda函数中 i 0 命名空间为:{'x': 1, 'i': 0}:
    Lambda函数中 i 1 命名空间为:{'x': 1, 'i': 1}:
    Lambda函数中 i 2 命名空间为:{'x': 1, 'i': 2}:
    Lambda函数中 i 3 命名空间为:{'x': 1, 'i': 3}:
    [0, 1, 2, 3]

    给内层函数 lambda_增加默认参数,命名空间中有了用来存储每次的 i , 即改成  def lambda_(x, i=i) :  这样每一次,
    内部循环生成一个lambda 函数时,都会把 i 作为默认参数传入lambda的命名空间
    循环4次实际lambda表达式为:
    第一次:lambda_( x, i=0) 第二次:lambda_(x, i=1) 第三次:lambda_(x, i=2) 第四次:lambda_(x, i=3)

    这样我们就能得到预计的结果:0, 1, 2, 3

    三、LEGB

    只有函数、类、模块会产生作用域,代码块不会产生作用域。作用域按照变量的定义位置可以划分为4类:

    Local(函数内部)局部作用域
    
    Enclosing(嵌套函数的外层函数内部)嵌套作用域(闭包)
    
    Global(模块全局)全局作用域
    
    Built-in(内建)内建作用域

    python解释器查找变量时,会按照顺序依次查找局部作用域--->嵌套作用域--->全局作用域--->内建作用域,在任意一个作用域中找到变量则停止查找,所有作用域查找完成没有找到对应的变量,则抛出 NameError: name 'xxxx' is not defined的异常。

    人生还有意义。那一定是还在找存在的理由
  • 相关阅读:
    Java基础语法学习18——方法(2)
    Java基础语法学习17——方法(1)
    Java基础语法学习16——二维数组
    Java8新特性Lambda表达式
    Web编程规范之三层架构设计规范
    Mybatis初体验
    Servlet快速入门:第一个Servlet程序
    Java中常用IO流之文件流的基本使用姿势
    Java中异常关键字throw和throws使用方式的理解
    Java中关于泛型集合类存储的总结
  • 原文地址:https://www.cnblogs.com/RyanJin/p/11887790.html
Copyright © 2011-2022 走看看