【问题标题】:Auto-bind scope variables in a closure在闭包中自动绑定范围变量
【发布时间】:2012-06-22 01:02:38
【问题描述】:

考虑一下这个(相当无意义的)javascript代码:

function make_closure() {
    var x = 123, y = 456;
    return function(varname) { return eval(varname) }
}

closure = make_closure()
closure("x") // 123
closure("y") // 456

make_closure 返回的函数不包含对范围变量的任何引用,但在调用时仍然能够返回它们的值。

有没有办法在 python 中做同样的事情?

def make_closure():
    x = 123
    return lambda varname: ...no "x" here...

closure = make_closure()
print closure("x") # 123

换句话说,如何编写一个闭包来“知道”定义范围中的变量而不明确提及它们?

【问题讨论】:

    标签: javascript python closures


    【解决方案1】:

    您可以在 Python 中关闭变量,这与在 Javascript 中的做法大致相同:

    def foo():
      x = 123
      return lambda y: x+y
    
    q = foo()
    
    print q(5)
    

    http://ideone.com/Q8iBg

    但是,存在一些差异 - 例如,您不能写入父范围内的变量(尝试写入只会创建一个本地范围的变量)。此外,Python 的 eval() 不包括父作用域。

    您可以通过从父作用域显式获取本地变量并将它们传递给子变量的 eval() 作为其 locals 参数来绕过 eval(),但这里真正的问题是您为什么需要这样做?

    【讨论】:

    • 尽管这不能直接回答这个问题,但事实是几乎所有程序都可以(并且应该)被重组为不需要 eval——在这种情况下,这个答案会告诉你你需要知道的一切关于闭包。
    • 同意。另外,我认为您的回答解决了这个问题,所以我投了赞成票。
    【解决方案2】:

    这可能是最接近完全等效的东西:

    def make_closure():
      x = 123
      l = locals()
      return lambda(varname): eval(varname, None, l)
    
    closure = make_closure()
    print closure("x") # 123
    

    最终,您的问题不在于您的本地闭包没有捕获 x,而是您的本地范围没有传递给 eval。

    有关 eval 函数的详细信息,请参阅http://docs.python.org/library/functions.html#eval

    不用说,这可能不是一个非常 Pythonic 的事情。你几乎从不想在 Python 中调用 eval ;如果你认为你这样做了,退后一步,重新考虑你更大的设计。但是当你确实需要使用它时,你需要了解它与 Javascript eval 的不同之处。它最终更强大——它可以用来模拟动态范围,或者作为 exec 的简化版本,或者在任意构建的环境中评估代码——但这也意味着它更棘手。

    【讨论】:

    • 是的,x 被闭包捕获了,但是 Python 似乎没有为我们提供证明这一点的方法(或相反)。
    • Python 提供了许多方法来证明正在发生的事情,即使不使用调试器或检查 locals() 参数。 Amber 的回答证明 x 的值要么被复制,要么被捕获。如果你把值改成可变的,比如[123]而不是123,你可以做closure()[0]=456,然后再次调用closure()得到[456]而不是[123],这就证明了实际捕获。这仍然不能告诉您 name 是否被捕获,因为 Python 使引用名称绑定变得困难且通常无用,但出于同样的原因,最终它并不重要。跨度>
    【解决方案3】:

    在 Python 2.x 中,这需要大量的组装。但是,在 Python 3.x 中,引入了“nonlocal”关键字以允许访问直接封闭范围内的变量。

    可能值得在这里查看更多信息:nonlocal keyword in Python 2.x

    【讨论】:

      【解决方案4】:

      人们已经提到使用locals()。另一种公认不太灵活的方法是在 Python 3 中使用新的 nonlocal 关键字:

      >>> def make_closure():
      ...     x = 123
      ...     def return_var(varname):
      ...         nonlocal x
      ...         return eval(varname)
      ...     return return_var
      ... 
      >>> closure = make_closure()
      >>> closure("x")
      123
      

      这有一个明显的缺点,即要求您从外部范围中显式标记要关闭和引用的变量。

      【讨论】:

      • 由于 OP 明确表示“……没有明确提及”,这并不能真正回答他想要的。尽管与 Amber 的回答一样,您可以争辩说它回答了他应该想要的。
      • @abarnert,这确实是一个有争议的问题。没有可以想象的情况(除非我的想象力真的让我失望),在这种情况下,编写这种函数的人无法获得需要关闭的变量的高级知识。使用 locals() 并没有根本的不同,它只是最大限度地包容并且不那么冗长。
      • @abarnert,仅供参考,我认为您的解决方案是更好(显然更简洁)的解决方案,因此我对此表示赞同。也就是说,我仍然认为使用 locals() 和更明确的 nonlocal 关键字方法之间的区别主要是学术性的。
      • 我可以很容易地想象出这样一种情况:想象一下你的本地环境已经被修改了,例如,做了大量的评估和执行。您无法(静态地)知道定义了哪些变量。这几乎正​​是 locals() 的用途。当然,这是一个非常不合情理的情况,但不比 OP 的问题更是如此。尝试让您的调用者访问您无法预测的变量与尝试访问您无法预测的变量……它们几乎是一回事。
      • @abarnert,这种假设情况与这个特殊情况无关,因为 OP 的问题让他在直接封闭的父函数中明确定义了他打算稍后引用的变量。但是,我当然不会与您争论,在其他涉及不同类型代码的情况下,显然需要使用 locals()
      猜你喜欢
      • 2021-04-16
      • 1970-01-01
      • 2012-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-13
      • 2015-02-20
      相关资源
      最近更新 更多