什么时候评估函数参数?

When are function arguments evaluated?

在此代码段中

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 时,它会在后面求值,而如果它是函数,它会先求值?我怎么理解这个?

  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, λ x: D(2, D(4, x)))(5))
    -> D(1, λ x: D(2, D(4, x)))(5)):
    打印(1)
    return λ x: D(2, D(4, x)))(5)
  2. print(lambda 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 函数的执行,但不是编程所必需的

在第一种情况下,您直接调用 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.

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

在第一个 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. 在第一种情况下,我们创建一个函数,其副作用是在返回 5 之前打印 4、2、1。
  2. 在第二种情况下,我们在t调用 Dc(1),立即输出1,然后调用t(5),输出4和2 返回前 5.

所有参数 表达式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 expression 只是计算那个函数,但不调用它。

>>> 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) 调用函数。此 then 从内到外评估正文中的嵌套调用:4, 2, 1.

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


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