【问题标题】:how does python scope work in this snippet?python 作用域在这个片段中是如何工作的?
【发布时间】:2017-12-02 10:48:51
【问题描述】:

我试图用函数来理解 python 作用域。跟随 sn-p 给了我一个意想不到的结果。

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 函数。这段代码是如何工作的?

【问题讨论】:

  • 您能解释一下您是如何达到预期结果的吗?
  • 您的 flist 的前 6 个值包含 lambdas。当您调用f() 在哪个阶段i==2j==5 即4s 和25s 时评估这些。在bar2 上,在我看来a(k) 返回一个lambda,其中n 已经设置......(所以你实际上是在附加lambda: foo(6), lambda: foo(7), ...)。但是,不确定这是如何解释的......

标签: python function lambda scope


【解决方案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 也是一个闭包,这意味着它会关闭其周围的上下文,即本地堆栈帧。

make_foo中的lambda只有make_foo里面的栈帧,里面只包含val

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

bar1 中,和以前一样,只有一个 lambda 和只有一个堆栈帧(bar1 只被调用一次)并且包含j 的堆栈帧以j 结束,指的是值@ 987654336@.

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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 这是否意味着表达式a(k) 每次都必须在bar2 中求值,所以这是唯一“有效”的 lambda?
    • 其实就是lambda-in-a-lambda; n foo 在每次调用 a 时都在一个新的上下文中调用,因此是一个区域变量,而其他所有 foo 都与封闭循环中的其他 foo 共享其参数。
    【解决方案3】:

    这里的原则是clousres。这是一个很容易阅读的内容https://www.programiz.com/python-programming/closure

    这是您的代码 sn-p 与一些 cmets 尝试解释该过程和“意外”输出:

    def foo(val): 返回值*值 飞盘 = [] for i in range(3): # 这个循环首先运行,i 在 2 处停止(因为 range 默认从 0 开始,在 3 之前停止) flist.append(lambda : foo(i)) # 我们附加一个 lambda 表达式,它调用 foo 的值为 i 三次 # 到目前为止,flist 在前三个索引中包含三个相同的 lambda 表达式。 # 然而 foo() 函数只在最后一次打印调用时被调用,然后当它去评估 i - # 它最后一次停在那里的时候是 2。 定义 bar1(): 对于范围内的 j (3,6): # 同样的原则在这里适用:三个插入到 flist 中的所有 j,到循环结束时将是 5 flist.append(lambda : foo(j)) def bar2(): 对于范围内的 k (6,9): a = lambda n: (lambda: foo(n)) # 这里是不同的,因为我们在循环中附加了 lambda 表达式的计算值 - # 我们不等待最后运行 foo()。这意味着 flist 将获得 foo(6)、foo(7)、- # 和 foo(8) 如果要在下面的打印表达式中计算,则只需 foo(8) 三次。 flist.append(a(k)) 酒吧1() 酒吧2() print([f() for f in flist])

    【讨论】:

      猜你喜欢
      • 2021-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多