我试图通过函数来理解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
函数。这段代码是如何工作的?
答案 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])