在Python中,为什么lambda表达式能引用正在定义的变量但不能引用列表?

29 投票
4 回答
4529 浏览
提问于 2025-04-17 21:50

这其实更多是出于好奇,但我刚刚注意到以下几点。如果我在定义一个自引用的lambda表达式,我可以很简单地做到:

>>> f = lambda: f
>>> f() is f
True

但是如果我想定义一个自引用的列表,就得用多个语句来完成:

>>> a = [a]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> a = []
>>> a.append(a)
>>> a[0] is a
True
>>> a
[[...]]

我还发现,这不仅仅限于列表,似乎除了lambda以外的任何表达式都不能引用赋值左边的变量。举个例子,如果你有一个只有一个节点的循环链表,你不能简单地这样写:

>>> class Node(object):
...     def __init__(self, next_node):
...         self.next = next_node
... 
>>> n = Node(n)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'n' is not defined

相反,你必须用两个语句来完成:

>>> n = Node(None)
>>> n.next = n
>>> n is n.next
True

有没有人知道这种差异背后的原因是什么?我明白递归的lambda使用得更频繁,因此支持自引用对lambda来说很重要,但为什么不允许任何赋值都这样呢?

补充:下面的回答很好地解释了这个问题。原因是,在Python中,lambda里的变量每次调用lambda时都会被计算,而不是在定义时计算。从这个意义上说,它们和用def定义的函数是完全一样的。我写了以下代码来实验一下这个是怎么工作的,包括lambda和def函数,希望能帮助大家更好地理解。

>>> f = lambda: f
>>> f() is f
True
>>> g = f
>>> f = "something else"
>>> g()
'something else'
>>> f = "hello"
>>> g()
'hello'
>>> f = g
>>> g() is f
True

>>> def f():
...     print(f)
... 
>>> f()
<function f at 0x10d125560>
>>> g = f
>>> g()
<function f at 0x10d125560>
>>> f = "test"
>>> g()
test
>>> f = "something else"
>>> g()
something else

4 个回答

2

这其实没什么特别的,只是这样运作而已。

一个lambda表达式和普通函数没什么区别。也就是说,我可以这样做:

x = 1
def f():
    print x + 2
f()
3
x = 2
f()
4

如你所见,在函数内部,x的值并没有预先定义——它是在我们真正运行f的时候才查找的。这也包括函数本身的值:我们不会在运行f之前去查找它代表什么,等到我们运行的时候,它已经存在了。

用lambda表达式做同样的事情也没有什么不同:

del x
f = lambda: x+2
f()
NameError: global name 'x' is not defined
x = 2
f()
4

效果是类似的。在这个例子中,我把x删掉了,所以在定义f的时候x不在作用域内,运行f时就正确地显示x不存在。但是在我们定义了x之后,f又可以正常工作了。

在列表的情况下就不一样了,因为我们实际上是在现在生成一个对象,所以右边的所有东西必须在现在就绑定。根据我对Python的理解(至少在实践中这样做是有用的),我们可以认为右边的所有东西都是被引用和绑定后再处理的,只有在这一切完成后,左边的值才会被绑定和赋值。

由于右边和左边是同一个值,当Python尝试绑定右边的名字时,它还不存在。

5

我们可以看到,当我们对这个lambda函数进行拆解时(在Python 2.6和3.3中输出是一样的)

>>> import dis
>>> f = lambda: f
>>> dis.dis(f)
  1           0 LOAD_GLOBAL              0 (f)
              3 RETURN_VALUE

我们演示了在调用这个函数之前,不需要加载f,因为它已经被全局定义并存储了,所以这样是可以工作的:

>>> f is f()
True

但是当我们这样做的时候:

>>> a = [a]    

如果a之前没有定义,就会出现错误。如果我们拆解Python的实现,

>>> def foo():
...     a = [a]
...     
>>> dis.dis(foo)
  2           0 LOAD_FAST                0 (a)
              3 BUILD_LIST               1
              6 STORE_FAST               0 (a)
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE    

我们会发现我们试图在存储a之前就去加载它。

9

因为 lambda 是一个函数,而函数的内容只有在调用这个函数的时候才会执行。

换句话说,另一种做法是这样的:

def f():
    return f

但是你说得对,不能在一个 表达式 中这样做,因为 def 是一个语句,所以不能在表达式中使用。

27

在一个lambda表达式里面的内容,只有在你调用这个函数的时候才会被计算,而不是在你定义它的时候。

换句话说,Python不会在你定义lambda的时候就去计算里面的内容f,而是等到你真正调用它的时候才会去计算。到那时,f已经在当前的作用域里定义好了(它就是这个lambda本身)。所以不会出现NameError的错误。


不过,像下面这样的代码就不一样了:

a = [a]  

当Python解释这类代码(叫做赋值语句)的时候,它会立即计算=右边的内容。而且,如果右边用到的名字在当前作用域里没有定义,就会出现NameError的错误。

撰写回答