为了好玩,我尝试用 lambda 函数创建一个链表。我尝试了这个小代码作为第一步,但在面临无限循环后我的实验很快就结束了:
import itertools
stack = lambda: ("c", None)
stack = lambda: ("b", stack)
stack = lambda: ("a", stack)
for _ in itertools.count():
_, stack = stack()
print(_)
预期输出:
a
b
c
TypeError: 'NoneType' object is not callable
实际产量:
a
a
a
...
我希望第二个 lambda 携带对第一个 lambda 的本地(对第二个 lambda)引用,第三个 lambda 携带对第二个 lambda 的本地(对第三个 lambda)引用。
出乎意料的是,第三个 lambda 返回对其自身的引用,从而使出栈永远在同一个 lambda 上循环。这不是预期的,因为堆栈在创建 lambda 时指向前一个 lambda。 lambda 如何在创建之前就拥有对自身的引用?
正如我们在Python Tutor(示例的永久链接)中看到的,每个 lambda 都被新的 lambda 替换,每次都引用自身(第一个返回 None 作为元组的第二个元素的除外):
这对我来说没有意义,因为 Python 的 Call by Object 行为,以及我如何理解 lambda 函数在其闭包中携带一个指向该值的单元格(例如,可通过
stack.__closure__
访问),但在本例中,没有闭包,甚至将代码放入函数中时也会创建一个引用 lambda 表达式本身的单元格:
>>> def make_stack():
... stack = lambda: ("c", None)
... stack = lambda: ("b", stack)
... stack = lambda: ("a", stack)
... return stack
...
>>> stack = make_stack()
>>> stack.__closure__[0].cell_contents is stack
True
lambda 怎么可能拥有对自身的引用,一个甚至还没有创建的对象?
lambda 表达式如何在实例化之前引用自身?
与函数调用自身执行递归调用的方式相同。
在 Python 中,函数的作用域(不仅仅是 lambda)仅限于其参数和来自外部作用域的任何引用。通过对象调用仅涉及参数,这意味着对外部作用域变量的 lambda(和函数)引用将访问这些变量的外部作用域值,而不是它们在函数定义中的当前值。因此,当调用 lambda(或函数)时,它会使用调用时引用变量的最新值。
例如:
>>> ham = lambda: bacon
>>> ham()
NameError: name 'bacon' is not defined
>>> bacon = 142
>>> ham()
142
这里,lambda 函数
ham
引用变量bacon
。当调用 ham
时,它返回 bacon
的当前值,即 142
,而不是定义 lambda 时的值。
与此类似:
>>> def ham():
... return bacon
>>> ham()
NameError: name 'bacon' is not defined
>>> bacon = 142
>>> ham()
142
这种行为也适用于闭包。即使 lambda 从已退出的作用域捕获变量,它也会保留对这些变量的引用。但是,它将始终使用变量的最新值。例如:
>>> def spam():
... ham = lambda: bacon
... bacon = 142
... return ham
...
>>> eggs = spam()
>>> eggs()
142
在此示例中,即使在函数 spam 退出后,lambda 函数
ham
仍保留对 bacon
的引用。当调用 eggs()
(返回的 lambda)时,它会计算引用 bacon
并返回其最后一个值 142
。
如果您希望 lambda 在定义时使用特定值,而不是跟踪稍后可能更改的变量,则可以在 lambda 中使用默认参数。这允许您将当前值显式传递给 lambda 并避免更新引用:
lambda spam=eggs: spam # return the value eggs had when the lambda was created
此技术“锁定”鸡蛋的价值作为默认参数垃圾邮件。
以下是应用此原理修复原始代码的示例:
import itertools
stack = lambda c="c", s=None: (c, s)
stack = lambda c="b", s=stack: (c, s)
stack = lambda c="a", s=stack: (c, s)
for _ in itertools.count():
_, stack = stack()
print(_)
输出:
a
b
c
TypeError: 'NoneType' object is not callable