【知识点】Python的闭包、匿名函数,问题:[lambda x:i*x for i in range(4)]

为什么会遇到 闭包、匿名函数这几个知识点?

最近学习python基础, 碰到一道代码题:

def func1():
    return [lambda x:i*x for i in range(4)]

print([m(2) for m in func1()])

其正确输出为什么? 正确答案为 : [ 6 , 6 , 6 , 6 ] :[6,6,6,6] :[6,6,6,6]
相比你能看到这个博客,很大概率遇到了类似的题目。

我们先说说包含哪些知识点:

  • 匿名函数lambda x:i*x中的 l a m b d a lambda lambda关键字
  • 闭包函数lambda x:i*x中调用了上一层函数for i in range(4)的局部命名空间的变量的 i i i
  • for循环对函数的迭代调用 :匿名函数部分以及[m(2) for m in func1()]

我们一步一步拆分理解:

一、for循环对函数的迭代调用

代码运行时,首先运行的便是print([m(2) for m in func1])
这表示对func1返回的列表进行遍历操作,具体要看其返回的是什么。
在调试Debug时, 也是运行到这后,进入先进入func1中去获取其返回。

二、匿名函数且触发闭包

def func1():
    return [lambda x:i*x for i in range(4)]

将上面函数简单化为:

def func1():
    list_i =[]
    for i in range(4):
        res = lambda x:x*i //匿名函数
        list_i.append(res)
    return list_i

可以看到首先出现了匿名函数,

匿名函数:python 不通过def来声明函数名字,而是通过lambda关键字来定义的函数称为匿名函数。 好处

  • 因为函数没有名字,不必担心函数名冲突
  • 匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。
  • 封装细节,提高安全性和可控性,(经常在全局作用域中被用于函数外部,从而限制向全局作用域中添加过多的变量和函数)。
  • 在全局作用域中使用块级作用域可以减少闭包占用内存的问题,(因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域链了)。
    具体可以参考Python函数学习——匿名函数

    Python 匿名函数详解

进一步看, 匿名函数调用了func1(上一级函数)中的局部变量 i, 这就出发了闭包

def func1():
    list_i =[]
    for i in range(4):
        res = lambda x:x*i //匿名函数
        list_i.append(res)
    return list_i

闭包的可以参考: Python技法,闭包

一开始,func1只是要返回一个列表, 真正调用到匿名函数的是m(2)去遍历这个列表时才进行操作, 也就是这一原因导致输出与想象的不同。

代码运行的具体流程

我们用以下代码查看,验证:

def func1():
    list_i =[]
    for i in range(4):
        def lambda_(x):
            print('等效匿名函数中 i {} 命名空间为:{}:'.format(i, locals()))
            res  = i*x
            return res 
        list_i.append(lambda_)
        print('变量 i 为:{} 命名空间为:{}'.format(i, locals()))

    return list_i
# 第一步执行逻辑,打印空间地址
list_a = func1()
print(list_a)
#代入数据计算
ans=[]
for m in list_a:
    res  = m(2)
    print("m  = {} \t  res={}".format(m, res))
    ans.append(res)
#打印最终结果
print(ans)

其输出结果为:

变量 i 为:0 命名空间为:{
    'list_i': [<function func1.<locals>.lambda_ at 0x000002222B22A040>], 
    'lambda_': <function func1.<locals>.lambda_ at 0x000002222B22A040>, 
    'i': 0}
变量 i 为:1 命名空间为:{
    'list_i': [<function func1.<locals>.lambda_ at 0x000002222B22A040>, 
               <function func1.<locals>.lambda_ at 0x000002222B22A0D0>],
    'lambda_': <function func1.<locals>.lambda_ at 0x000002222B22A0D0>, 
    'i': 1}
变量 i 为:2 命名空间为:{
    'list_i': [<function func1.<locals>.lambda_ at 0x000002222B22A040>, 
               <function func1.<locals>.lambda_ at 0x000002222B22A0D0>,
               <function func1.<locals>.lambda_ at 0x000002222B22A160>], 
   'lambda_': <function func1.<locals>.lambda_ at 0x000002222B22A160>, 
   'i': 2}
变量 i 为:3 命名空间为:{
    'list_i': [<function func1.<locals>.lambda_ at 0x000002222B22A040>, 
               <function func1.<locals>.lambda_ at 0x000002222B22A0D0>, 
               <function func1.<locals>.lambda_ at 0x000002222B22A160>, 
               <function func1.<locals>.lambda_ at 0x000002222B22A1F0>], 
    'lambda_': <function func1.<locals>.lambda_ at 0x000002222B22A1F0>, 
    'i': 3}

[   <function func1.<locals>.lambda_ at 0x000002222B22A040>, 
    <function func1.<locals>.lambda_ at 0x000002222B22A0D0>, 
    <function func1.<locals>.lambda_ at 0x000002222B22A160>, 
    <function func1.<locals>.lambda_ at 0x000002222B22A1F0>]
    
    等效匿名函数中 i 3 命名空间为:{'x': 2, 'i': 3}:
    m  = <function func1.<locals>.lambda_ at 0x000002222B22A040>      res=6
    等效匿名函数中 i 3 命名空间为:{'x': 2, 'i': 3}:
    m  = <function func1.<locals>.lambda_ at 0x000002222B22A0D0>      res=6
    等效匿名函数中 i 3 命名空间为:{'x': 2, 'i': 3}:
    m  = <function func1.<locals>.lambda_ at 0x000002222B22A160>      res=6
    等效匿名函数中 i 3 命名空间为:{'x': 2, 'i': 3}:
    m  = <function func1.<locals>.lambda_ at 0x000002222B22A1F0>      res=6
    [6, 6, 6, 6]

其操作步骤如下图所示:

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

关于是否为闭包的问题

我自己认为, 按定义,本题应该是触发了闭包。

  • 与闭包无关
    也有博主 [ a l o a l o l o l ] ^{[aloalolol]} [aloalolol]的文章分析将func1去掉证明不是闭包,但是我感觉他的改动依然触发了闭包,因为调用了外层函数的变量,即便外层函数是指主函数的语句。
    他的博文如下: aloalololo-与闭包无关
    这篇博文写的很好,我自己也是通过这篇文章厘清了为什么会是【6,6,6,6】
    但我不太认同跟闭包没关系,比方说例子:
def func(x):
    return x +i
ans= []
for i in range(4):
    res = func(2)
    print("i=",i,"res=",res)
    ans.append(res)

print(ans)

输出:

i= 0 res= 2
i= 1 res= 3
i= 2 res= 4
i= 3 res= 5
[2, 3, 4, 5]
ans= []
for i in range(4):
    def func(x):
        return x +i
    ans.append(func(2))

print(ans)

输出:

[2, 3, 4, 5]

并没有像本次遇到的顺序问题,所以我以为应该是闭包原因。

总结

以上就是我的学习和理解路径,虽然我现在也不敢完全肯定说是闭包的原因,但是至少下次遇到这类题,不至于抓瞎,博主 [ 裸睡的雨 ] ^{[裸睡的雨]} [裸睡的雨]帖子下有一些讨论, 大家可以看一下, 如果有哪位大佬有正确答案,希望可以@我一下,让我也学习一下。

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值