Discuss / Python / 关于闭包易错点的分析和总结

关于闭包易错点的分析和总结

Topic source

凡响skyline

#1 Created at ... [Delete] [Delete and Lock User]
def count():
    # fs里最终存的是循环创建未被调用的三个函数f
    fs = []
    for i in range(1, 4):
        # 每次循环都是在创建函数f但并没有调用
        def f():
            return i * i
        # 此时函数f并没有被调用,添加的是函数f本身,不是f的调用结果
        fs.append(f)
        # 也就是说count()得到的返回值是三个函数
    # 循环结束i为3
    return fs

print(count())

#   易错点1
# 运行print(count())你以为会输出[1,4,9],而实际上打印了由三个函数构成的列表
# 如果要想打印[1,4,9],应该将fs.append(f)里的f改为f()

#   易错点2
# 如果不改原代码,而是运行以下代码,结果还是1,4,9吗
# 将count()的返回值(三个函数f)分别赋给三个变量
# f1, f2, f3 = count()
# 打印调用三个函数的结果
# print(f1(), f2(), f3())
# 结果为: 9 9 9 而不是:1 4 9
# 原因是循环里i由1增加到3这个过程只创建了三个函数而未立即调用
# 函数f虽然引用了i,但当循环结束i已经变为,故返回fs时都只会取到i为3
# 综上返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量
# 如果一定要引用循环变量就再创建一个函数,用该函数的参数绑定循环变量当前的值
# 无论该循环变量后续如何更改,已绑定到函数参数的值不变:
# def count():
#     def f(j):
#         def g():
#             return j * j
#         return g
#     fs = []
#     for i in range(1, 4):
#         # f(i)立刻被执行,因此i的当前值被传入f()
#         fs.append(f(i))
#     # 虽然i的值是变化的,但是i在循环的每个值时传入f(i)被绑定给了j
#     # 故后续j不会受到i的影响
#     return fs
f1, f2, f3 = count()

这个地方我看不懂,这不是相当于

f1= count()

f2=count()

f3=count()

为什么跟循环有关。运行到return不是都加三个函数到fs 了嘛?

我零基础所以不太懂T.T

凡响skyline

#3 Created at ... [Delete] [Delete and Lock User]
"""
count()的值就是调用count函数得到的返回值fs
而fs又是由三个未经调用的函数对象构成的列表
[<function count.<locals>.f at 0x0000020E3DB09AF0>, <function count.<locals>.f at 0x0000020E3DB09B80>, <function count.<locals>.f at 0x0000020E3DB09C10>]
f1, f2, f3 = count()这样赋值叫拆包
相当于:
f1 = count()[0]
f2 = count()[1]
f3 = count()[2]
相当于:
f1 = fs[0]
f2 = fs[1]
f3 = fs[2]
"""
# 另外关于赋值
# 可以连等:a=b=c=1
# 可以用可迭代对象拆包分别赋值:a,b,c = 1,2,3相当于a,b,c=[1,2,3]相当于a,b,c = (1,2,3)
# 变量前加*号代表可变长度,则有
# >>> a, *b, c = [1, 2, 3, 4]
# >>> print(a, b, c)
# 1 [2, 3] 4

我个人理解:

一、函数定义时,并不会保留外部变量的值,而for循环中,正是在定义函数

二、函数返回时,才将相关变量都保存在返回的函数中,此时for循环已经执行完毕

三、函数调用时,才会访问相关变量的值,i = 3

但是!!!函数定义时其实是有一个地方可以保存变量的值的,那就是作为默认参数的默认值

比较简单的写法:

def count():
    fs = []
    for i in range(1, 4):
        def f(n=i):     # 将变量值作为默认参数的值
            return n * n
        fs.append(f)    
    return fs

请做一下下面这道题:

L = [lambda : x ** 2 for x in range(1, 4)]

参考文档: https://docs.python.org/zh-cn/3/faq/programming.html#how-can-i-pass-optional-or-keyword-parameters-from-one-function-to-another

抱歉,按照上面那种写法就不是闭包函数了,大家注意一下。

def count():
    fs = []
    for i in range(1, 4):
        def f(n=i):     # 将变量值作为默认参数的值
            return n * n
        fs.append(f)    
    return fs


c = count()

for i in c:
    print(i.__closure__)  # None  None  None

Hannibal2077

#6 Created at ... [Delete] [Delete and Lock User]

兄弟好牛


  • 1

Reply