在Python中,为什么lambda表达式能引用正在定义的变量但不能引用列表?
这其实更多是出于好奇,但我刚刚注意到以下几点。如果我在定义一个自引用的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 个回答
这其实没什么特别的,只是这样运作而已。
一个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尝试绑定右边的名字时,它还不存在。
我们可以看到,当我们对这个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
之前就去加载它。
因为 lambda 是一个函数,而函数的内容只有在调用这个函数的时候才会执行。
换句话说,另一种做法是这样的:
def f():
return f
但是你说得对,不能在一个 表达式 中这样做,因为 def
是一个语句,所以不能在表达式中使用。
在一个lambda表达式里面的内容,只有在你调用这个函数的时候才会被计算,而不是在你定义它的时候。
换句话说,Python不会在你定义lambda的时候就去计算里面的内容f
,而是等到你真正调用它的时候才会去计算。到那时,f
已经在当前的作用域里定义好了(它就是这个lambda本身)。所以不会出现NameError
的错误。
不过,像下面这样的代码就不一样了:
a = [a]
当Python解释这类代码(叫做赋值语句)的时候,它会立即计算=
右边的内容。而且,如果右边用到的名字在当前作用域里没有定义,就会出现NameError
的错误。