python范围如何在这个片段中工作?

时间:2017-12-02 10:48:51

标签: python function lambda scope

我试图通过函数来​​理解python范围。以下片段给了我一个意想不到的结果。

def foo(val):
    return  val*val

flist = []

for i in range(3):
    flist.append(lambda : foo(i))

def bar1():
    for j in range(3,6):
        flist.append(lambda : foo(j))

def bar2():
    for k in range(6,9):
        a = lambda n: (lambda:foo(n))
        flist.append(a(k))

bar1()
bar2()

print([f() for f in flist])

预期结果是:

  

[0,1,4,9,16,25,36,49,64]

但得到了:

  

[4,4,4,25,25,25,36,49,64]

代替。在前两种情况下,循环变量的最后一个值传递给foo函数。这段代码是如何工作的?

3 个答案:

答案 0 :(得分:1)

在第一个循环中,您追加的每个lambda都使用相同的变量,因此所有3个实例都使用其最终值;在bar1中也是如此(但它使用的变量与使用的第一个循环不同)。

答案 1 :(得分:1)

在我重新安排原始代码之后,我已经设法理解了发生了什么:

def foo(val):
    return  val*val

def make_foo(val):
    return lambda : foo(val)


flist = []

for i in range(3):
    flist.append(make_foo(i))

def bar1():
    for j in range(3,6):
        flist.append(lambda : foo(j))

def bar2():
    a = lambda n: (lambda:foo(n))
    for k in range(6,9):
        flist.append(a(k))

bar1()
bar2()

print([f() for f in flist])

输出:

[0, 1, 4, 25, 25, 25, 36, 49, 64]

请注意输出仅略有改变:[4,4,4 - > [0,1,4

原因是任何lambda也是一个闭包,这意味着它会关闭其周围的上下文,即本地堆栈帧。

lambda中的make_foo只有make_foo内部的堆栈框架,其中只包含val

在第一个循环中,make_foo被调用三次,因此每次创建不同的堆栈帧时,val都会引用不同的值。

bar1中,和以前一样,只有一个lambda,只有一个堆栈帧(bar1只被调用一次),包含j的堆栈帧最后以{{ 1}}引用值j

5中,我已明确显示bar2引用单个a,但该lambda虽然引用了本地堆栈帧,但并未引用任何局部变量。在循环期间,lambda lambda实际上被调用,但是返回另一个lambda,而lambda又引用另一个堆栈帧。该堆栈帧是a内的新帧,每个堆栈帧都包含a,与n一样,指的是不同的值。

另一个需要注意的重要事项是每个lambda都存储在make_foo中。由于flist指的是所有lambdas,所以它们引用的所有东西也仍然存在。这意味着所有这些堆栈帧仍然存在,以及堆栈帧引用的任何局部变量。

答案 2 :(得分:0)

这里的原则是clousres。这里有一个非常简单的阅读https://www.programiz.com/python-programming/closure

这是您的代码段,其中包含一些注释,可以尝试解释该过程以及"意外的"输出:

def foo(val):
    return val*val

flist = []

for i in range(3):  # This loop runs first and i stops at 2 (as range defaults to start at 0 and stops before 3)
    flist.append(lambda : foo(i)) # we append a lambda expression that invokes foo with value of i three times
    # So so far flist contains three identical lambda expression in the first three indexes.
    # However the foo() function is being called only on the last print call and then and when it goes to evaluate i -
    # it's 2 for as it last stoped there.

def bar1():
    for j in range(3,6):
        # same principle applies here: three insertions to the flist all with j, that by the end of the loop will be 5
        flist.append(lambda : foo(j))

def bar2():
    for k in range(6,9):
        a = lambda n: (lambda: foo(n))
        # here it is deferent as we append the evaluated value of the lambda expression while we are still in the loop -
        # and we are not waiting to run foo() at the end. That means that flist will get the values of foo(6), foo(7), -
        # and foo(8) and just foo(8) three times if it was to be evaluated in the the print expression below.
        flist.append(a(k))

bar1()
bar2()

print([f() for f in flist])