什么时候评估函数参数?
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 时,它会在后面求值,而如果它是函数,它会先求值?我怎么理解这个?
- print((lambda x: D(1, D(2, D(4, x))))(5)) = print((D(1, D(2, D(4, 5) ))))
- 打印:4 执行:打印((D(1, D(2, 5))))
- 打印:2 执行:打印((D(1, 5)))
- 打印:1 执行:打印(5)
- 打印:5
输出:-> 4,2,1,5
- 打印(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)
- print(lambda x: D(2, D(4, x)))(5))
3...
并不是它们的执行方式不同,您的代码中有两个不同的 lambda 函数:
- x -> D(1, D(2, D(4, x)))
- 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))
现在很容易看出会发生什么
- 在第一种情况下,我们创建一个函数,其副作用是在返回 5 之前打印 4、2、1。
- 在第二种情况下,我们在
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
等引用也是表达式。他们分别评估他们的价值和指称。
在此代码段中
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((lambda x: D(1, D(2, D(4, x))))(5)) = print((D(1, D(2, D(4, 5) ))))
- 打印:4 执行:打印((D(1, D(2, 5))))
- 打印:2 执行:打印((D(1, 5)))
- 打印:1 执行:打印(5)
- 打印:5
输出:-> 4,2,1,5
- 打印(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) - print(lambda x: D(2, D(4, x)))(5)) 3...
并不是它们的执行方式不同,您的代码中有两个不同的 lambda 函数:
- x -> D(1, D(2, D(4, x)))
- 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))
现在很容易看出会发生什么
- 在第一种情况下,我们创建一个函数,其副作用是在返回 5 之前打印 4、2、1。
- 在第二种情况下,我们在
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
等引用也是表达式。他们分别评估他们的价值和指称。