【问题标题】:Dynamic function creation and function body evaluation动态函数创建和函数体评估
【发布时间】:2015-07-17 02:39:29
【问题描述】:

考虑以下 Python 3 指令

res = []
for a in range(3) :
    res.append(lambda n : n + a)

目的是构建一个列表res,其中包含三个函数res[0]res[1]res[2],使得res[i](n) 返回n + i,对于[0, 1, 2] 中的所有i

但是,一个人得到了

>>> res[0](0)
2

而不是

>>> res[0](0)
0

一个也有

>>> res[1](2)
4

对此行为的解释是,在示例的任何动态生成的匿名函数的主体中的表达式n + a 中,符号a 在函数创建时不会被计算。评估在 for 语句的出口处执行,解释了为什么所有函数 res[0]res[1]res[2] 返回它们的参数值加上 2è (becauseabrowsesrange(3)and2 ` 是它的最后一个值)。

请注意,问题不在于我们使用匿名函数这一事实。确实,说明书

res = []
for a in range(3) :
    def f(n) :
        return n + a
    res.append(f)

导致相同的行为。

还请注意,我们可以通过使用 Python 的函数 eval 来实现上述目标:

res = []
for a in range(3) :
    s = "lambda n : n + %s" %(a)
    res.append(eval(s))

诀窍在于a 的动态值被考虑用于创建res 的每个函数。

现在我的问题是

  1. 这是错误还是功能?
  2. 是否有其他方法不通过eval 来获得预期的行为?

【问题讨论】:

标签: python function dynamic


【解决方案1】:

我不能和 #1 说话......除了说我确信这是有意和可取的某些定义的有意和可取的行为

但是 WRT #2

res = []
for a in range(3) :
    res.append(lambda n,b=a : n + b)

res[0](0)

这将在 for 循环内(而不是退出后)将 a 评估为默认的第二个参数...

粗略的这让它对类似的东西开放

res[0](0,8)

【讨论】:

    【解决方案2】:

    因为 closure 动态函数引用变量 a 并在执行时使用它在 for 循环结束时的最终值。

    您可以通过将其设置为具有默认值的函数参数来防止这种情况发生,这样就不需要在调用时提供它。定义每个动态函数时a 的值将成为使用的值。这就是我的意思:

    res = []
    for a in range(3) :
        res.append(lambda n, a=a: n + a)
    
    print(res[0](0))  # -> 0
    print(res[1](2))  # -> 3
    

    【讨论】:

    • 这使它成为一个有两个参数的函数。不太干净。
    • @StefanPochmann:并非如此,如最后对函数的示例调用所示。这是因为具有默认值的参数的值是可选的。
    • 是的,它是可选的,但它仍然存在。
    • @StefanPochmann:当然,您有权发表自己的少数意见,但这是处理此类问题的常用 Python 习语,而且非常轻量级。
    • 好的,谢谢,我测试过它确实比我的更小更快。我会记住的。
    【解决方案3】:

    如果您不想使用lambda,这也可以:

    >>> from functools import partial
    >>> for a in range(3) :
        def f(a, n):
            return n + a
        f = partial(f, a)
        res.append(f)
    
    >>> res[0](0)
    0
    >>> res[1](2)
    3
    

    【讨论】:

      【解决方案4】:

      这为您提供了只有一个参数的干净函数:

      res = []
      for a in range(3) :
          res.append((lambda a: lambda n: n+a)(a))
      

      不过,这样做可能会更好地提高可读性和效率:

      def adder(amount):
          return lambda n: n + amount
      
      res = []
      for a in range(3) :
          res.append(adder(a))
      

      另外,您的 “在 for 语句的出口处执行评估” 是错误的。在循环之后尝试打印,然后进一步增加a,然后再次打印。您会看到这些函数现在使用了进一步增加的 a 值(当然,仅适用于您的版本,而不适用于我的版本)。这是因为您的函数没有自己的 a,而是使用相同的全局 a,并在调用它们时对其进行评估。

      【讨论】:

        猜你喜欢
        • 2021-02-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-31
        相关资源
        最近更新 更多