lambda 表达式如何在实例化之前引用自身?

问题描述 投票:0回答:1

为了好玩,我尝试用 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 tutor screenshot showing memory map of the script

这对我来说没有意义,因为 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 怎么可能拥有对自身的引用,一个甚至还没有创建的对象?

python lambda linked-list
1个回答
0
投票

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
© www.soinside.com 2019 - 2024. All rights reserved.