【问题标题】:Have Python 2.7 functions remember value and not reference? Closure WeirdnessPython 2.7 函数是否记住值而不是引用?关闭怪异
【发布时间】:2011-10-07 21:41:19
【问题描述】:

我试图从一个函数返回一个函数列表,每个函数都使用外部范围的变量。这是行不通的。这是一个演示正在发生的事情的示例:

a = []
for i in range(10):
    a.append(lambda x: x+i)
a[1](1) # returns 10, where it seems it should return 2

为什么会发生这种情况,如何在 python 2.7 中解决它?

【问题讨论】:

    标签: python functional-programming scope closures


    【解决方案1】:

    i 每次都引用同一个变量,所以 i 在所有 lambda 表达式中都是 9,因为这是循环结束时 i 的值。最简单的解决方法是使用默认参数:

    lambda x, i=i: x+i
    

    这会将循环的 i 的值绑定到 lambda 定义时的局部变量 i

    另一种解决方法是定义一个定义另一个 lambda 的 lambda,并调用第一个 lambda:

    (lambda i: lambda x: x+i)(i)
    

    如果您考虑这一点,这种行为会更有意义:

    def outerfunc():
    
        def innerfunc():
            return x+i
    
        a = []
        for i in range(10):
            a.append(innerfunc)
        return a
    

    这里,innerfunc 定义了一次,因此直观的感觉是您只使用一个函数对象,并且您不会期望循环创建十个不同的闭包。使用 lambda,它看起来 不像函数只定义一次,看起来你每次都在循环中重新定义它,但实际上它在功能上与 long 相同版本。

    【讨论】:

    • 嗯。真的没有办法让每个 lambda 保持自己的价值吗?我认为这就是结束的全部意义。
    • @Chironex,闭包在变量名而不是值上
    • 所有声称具有闭包功能的编程语言都是这种情况吗?
    • @Chironex - 这是他们的观点,但它不像你想象的那样工作! :)
    • @phkahler:您正在为新定义的函数参数分配默认值。然后该值将存储在函数对象中,而不是使用词法范围规则。
    【解决方案2】:

    因为当您定义匿名函数(lambda 表达式)时,i 没有被评估,而是在它被调用时。您可以通过在a[1](1) 之前添加del i 来查看这一点:您将在a[1](1) 行上获得NameError: global name 'i' is not defined

    你需要每次都将 i 的值固定到 lambda 表达式中,如下所示:

    a = [lambda x, i=i: x+i for i in range(10)]
    a[1](1) # returns 2
    

    【讨论】:

      【解决方案3】:

      另一种更通用的解决方案 - 也没有 lambda:

      import operator
      from functools import partial
      a = []
      for i in range(10):
          a.append(partial(operator.add, i))
      a[1][(1) # returns 2
      

      这里的关键方面是functools.partial

      【讨论】:

      • partial(operator.add, i) 等价于(lambda i: lambda *args, **kwargs: operator.add(i, *args, **kwargs))() 但这绝对更漂亮,并且内置函数在可能的情况下比 lambda 表达式更好。我不会说这没有 lambdas:如果操作很复杂,它仍然会涉及某种类型的函数定义。
      • 我认为最后一对括号中应该有一个i。至于 lambdas:它至少没有 (double) lambdas 构造;) 而其中的 double 让我真的不寒而栗 :)
      • @agf:是的,5 分钟过去了 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-14
      • 2017-10-04
      相关资源
      最近更新 更多