【问题标题】:When are function arguments evaluated?何时评估函数参数?
【发布时间】:2021-09-04 12:26:35
【问题描述】:

在这段代码中sn-p

def D(m, x):                                                                                                                                                                                                                                                                                                                                                                               
    print(m)                                                                                                                                                                                                                                                                                                                                                                               
    return x                                                                                                                                                                                                                                                                                                                                                                               
                                                                                                                                                                                                                                                                                                                                                                                           
print((lambda x: D(1, D(2, D(4, x))))(5))                                                                                                                                                                                                                                                                                                                                                  
print("\n\n\n")                                                                                                                                                                                                                                                                                                                                                                            
print(D(1, lambda x: D(2, D(4, x)))(5))   

我们看到以下输出

4
2
1
5




1
4
2
5

为什么输出顺序不同?看起来当 D 的参数是 lambda 时,它会在稍后被评估,而如果它是一个函数,它会先被评估?我怎么理解这个?

【问题讨论】:

  • 如果您使用print(m, x) 而不仅仅是print(m),事情应该会更明显。一般来说,通过print、调试器或类似工具查看整个图片,而不是在您不理解的情况下尝试推断,效果会更好。
  • 我明白发生了什么。我想了解为什么会这样。这不是这里的问题

标签: python function lambda higher-order-functions


【解决方案1】:

所有参数在函数被实际调用时计算。

在第一个 print 中,我们有一个由 lambda 表达式定义的函数以应用于 5。

在第二个print 中,我们需要对D 进行调用,以便获取一个应用于5 的函数。


D 是什么?它基本上是一个标识函数,在返回另一个值之前打印一个值作为副作用。我们稍微改写一下:

def Dc(m):
    def identity(x):
        return x
    print(m)
    return identity

花点时间说服自己Dc(m)(x) 等同于D(m, x)

现在让我们用Dc 代替D 重写您的原始示例。

print((lambda x: Dc(1)(Dc(2)(Dc(4)(x))))(5))
print(Dc(1)(lambda x: Dc(2)(Dc(4)(x)))(5))

如果我们假装 Python 是一个函数组合操作符会更容易看出。

f ∘ g = lambda x: f(g(x))

(f ∘ g)(x) 只是调用g(x),然后将结果传递给f

重要提示:请记住,g 的任何副作用都会在 f 的副作用之前发生。

现在让我们使用新的合成运算符重写我们的两个示例。

print((Dc(1) ∘  Dc(2) ∘ Dc(4))(5)) # print(t(5)) where t = Dc(1) ∘  Dc(2) ∘ Dc(4)                                                                                                                                                                                                                                                                                                                   
print(Dc(1)((Dc(2) ∘ Dc(4))(5))  # print(Dc(1)(t)(5)) where t = Dc(2) ∘ Dc(4)

由于组合本身不产生副作用,而且函数组合具有关联性,我们可以将Dc(2) ∘ Dc(4)的定义分解出来,使其更易于阅读。

t = Dc(2) ∘ Dc(4)
print((Dc(1) ∘ t)(5)
print(Dc(1)(t)(5))

现在很容易看到发生了什么

  1. 在第一种情况下,我们创建一个函数,其副作用是打印 4、2、1,然后返回要打印的 5。
  2. 在第二种情况下,我们调用t上的Dc(1),立即输出1,然后调用t(5),在返回5之前​​输出4和2。

【讨论】:

    【解决方案2】:

    第一种情况,你是直接调用lambda函数:

    (lambda x: D(1, D(2, D(4, x))))(5)
    

    由于您正在调用它,因此传递了值 5,并且 lambda 内的块按 D4->D2->D` 的顺序执行,因此打印了 4、2、1 和 5。 lambda 中第一次调用 D 的第二个参数会立即执行,因为它是一个调用,而不是对 lambda 的引用。

    在第二种情况下,您正在调用普通的 python 函数并将调用作为参数传递给 lambda:

    D(1, lambda x: D(2, D(4, x)))(5)
    

    在这种情况下,您没有调用 lambda,而是将调用传递给 lambda,这本质上是不同的,这就是为什么顺序是从 D1->D2->D4 并且由于 x 被传递给 D4,打印的第一个值是 1,因为外部调用是 D1,然后当 D1 中需要 x 时,对 lambda 的调用在 x 被执行时传递给 D1,剩余的打印顺序是 4,2 和 5。

    【讨论】:

      【解决方案3】:

      所有参数表达式1在将它们传递给调用之前进行评估。

      >>> print(5)      # 5 is passed to print
      5
      >>> print(3 + 2)  # expression `3 + 2` evaluates to 5. 5 is passed to print
      5
      

      这也适用于递归:如果参数表达式需要调用,则计算嵌套调用的参数表达式,将其结果传递给嵌套调用,然后用于计算外部参数表达式。

      >>> #         abs(-2) => 2
      >>> #     3 + 2 => 5
      >>> #     5
      >>> print(3 + abs(-2))
      5
      >>> print("The result of calling print(5) is", print(5))
      5
      The result of calling print(5) is None
      

      由于函数表达式密切相关,可能会引起一些混淆——通俗地说,人们可能偶尔会说“评估一个函数”。因此,区分“将函数求值的表达式”和“涉及调用函数的表达式”是很重要的。

      >>> print("The value of `abs(-3)` is", abs(-3))  # expression `abs(-3)` evaluates calling a function
      The value of `abs(-3)` is 3
      >>> print("The value of `abs` is", abs)          # expression `abs` evaluates to a function
      The value of `abs` is <built-in function abs>
      

      值得注意的是,将 计算为 函数的表达式仅提供该函数,而不计算该函数内部的任何内容。事实上它通常不能:评估函数的主体通常需要参数。

      这个区别在处理“lambda”时很重要:有lambda表达式可以被求值以创建一个函数,还有lambda函数 就是这样一个表达式的结果。像lambda x: print("lambda x got", x) 这样的lambda 表达式 只计算该函数但不调用它。

      >>> lambda x: print("lambda x got", x)       # lambda expression => lambda function
      <function <lambda> at 0x10f055670>
      >>> (lambda x: print("lambda x got", x))     # nested lambda expression => lambda function
      <function <lambda> at 0x10f055670>
      >>> (lambda x: print("lambda x got", x))(5)  # called lambda expression => execution
      lambda x got 5
      

      要查看两个示例案例之间的评估差异,将它们重写为 a) 视觉上独立的表达式和 b) 省略常见的嵌套部分会有所帮助。

      print(
          (lambda x:
              D(1, ...)  # D(1 is inside lambda
          )(5)
      )
      print(
          D(1,                # lambda is inside D(1
              lambda x: ...)
          (5)
      ) 
      

      在第一种情况下,lambda x: … 被求值并立即通过(5) 调用该函数。这然后从内到外评估正文中的嵌套调用:4、2、1

      在第二种情况下,lambda x: … 被评估并传递给D(1, …) 而不调用它;相反,调用D(1, …) 被评估,打印它的第一个参数1 并返回它的第二个参数lambda x: …。只有这样才能通过(5) 调用 lambda,从内到外评估主体中的嵌套调用:4, 2


      1从技术上讲,5 等文字和abs 等引用也是表达式。它们分别评估它们的价值和参照物。

      【讨论】:

        【解决方案4】:
        1. print((lambda x: D(1, D(2, D(4, x))))(5)) = print((D(1, D(2, D(4, 5))) ))
        2. 打印:4 次执行:打印((D(1, D(2, 5))))
        3. 打印:2 执行:打印((D(1, 5)))
        4. 打印:1 次执行:打印(5)
        5. 打印:5

        输出:-> 4,2,1,5

        1. 打印(D(1, lambda x: D(2, D(4, x)))(5))
          -> D(1, λ x: D(2, D(4, x)))(5)):
          打印(1)
          返回 lambda x: D(2, D(4, x)))(5)
        2. 打印(λ x: D(2, D(4, x)))(5)) 3...

        并不是它们的执行方式不同,你的代码中有两个不同的 lambda 函数:

        1. x -> D(1, D(2, D(4, x)))
        2. x -> D(2, D(4, x))

        导致不同的输出。

        如果您对背后的理论感兴趣:

        https://plato.stanford.edu/entries/lambda-calculus/

        它确实有助于理解 lambda 函数的执行,但对于编程来说不是必需的

        【讨论】:

          猜你喜欢
          • 2015-12-01
          • 2013-08-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-07-31
          • 2014-09-14
          • 2011-02-12
          • 1970-01-01
          相关资源
          最近更新 更多