我认为当你将
i
看作一个
名称 而不是某种
值 时会发生什么,这是相当明显的。你的 lambda 函数执行类似于“取 x:查找 i 的值,计算 i**x”...所以当你实际运行函数时,它会刚刚
在那个时刻 查找
i
,因此
i
是
4
。
你也可以使用当前数字,但必须让 Python 将其绑定到另一个名称上:
def makeActions():
def make_lambda( j ):
return lambda x: j * x
acts = []
for i in range(5):
acts.append(make_lambda(i))
return acts
这可能会让人感到困惑,因为通常你被教导变量和它的值是相同的东西 - 在实际使用变量的语言中确实如此,但Python没有变量,而是使用名称。
关于您的评论,实际上我可以更好地说明这一点:
i = 5
myList = [i, i, i]
i = 6
print(myList) # myList is still [5, 5, 5].
你说你把 "i" 改成了 6,但实际情况并非如此:
i=6
的意思是“我有一个值为
6
,我想把它命名为
i
”。你之前使用过
i
这个名称对 Python 来说毫无意义,它只会重新分配这个名称,而不会改变其值(这仅适用于变量)。
你可以这样说,在
myList = [i, i, i]
中,无论
i
当前指向哪个值(数字 5),都将为其赋予三个新名称:
mylist[0]、mylist[1]、mylist[2]
。当你调用函数时也是同样的道理:参数被赋予新名称。但这可能与列表的任何直觉相违背......
这可以解释示例中的行为:你分别为
mylist[0]=5
、
mylist[1]=5
、
mylist[2]=5
赋值,因此当你重新分配
i
时它们不会改变。如果
i
是一些可变的东西,例如列表,则更改
i
将反映在
myList
中的所有条目上,因为你只是为同一值分配了不同的名称!
你可以用
mylist[0]
左侧的
=
证明它是一个名称。我喜欢把
=
称为
分配名称运算符:它在左边接受名称,在右边接受表达式,然后评估表达式(调用函数,查找名称背后的值),直到它有一个值,最后将名称赋给该值。它什么都不会改变。
关于 Mark 的评论:
当我们有一些可寻址的内存时,引用(和指针)才有意义。值存储在内存中的某个地方,引用将您带到那个位置。使用引用意味着转到内存中的那个位置并对其进行操作。问题是 Python 根本不使用这些概念!
Python 虚拟机没有内存概念——值在空间中浮动,名称是连接它们的小标签(通过一根小红线)。名称和值存在于不同的世界!
这在编译函数时有很大的区别。如果有引用,您就知道所引用对象的内存位置。然后,您可以简单地用该位置替换引用。
另一方面,名称没有位置,因此您必须(在运行时)跟随那条小红线并使用在另一端的任何内容。这就是 Python 编译函数的方式:在代码中存在名称的地方,它会添加一条指令来确定该名称代表什么。
因此,基本上 Python 确实完全编译函数,但名称被编译为查找嵌套命名空间中的名称,而不是某种内存引用。
当您使用一个名称时,Python 编译器会尝试找出它属于哪个命名空间。这导致一个指令从找到的命名空间中加载该名称。
这让您回到原始问题:在
lambda x:x**i
中,
i
被编译为在
makeActions
命名空间中进行查找(因为在那里使用了
i
)。Python 不知道也不
i=i
参数默认值习惯性地实现早期绑定——这有什么不好的呢?特别是在 py3 中,新的nonlocal
让内部函数也能重新绑定这些变量,如果强制早期绑定,那将是一场灾难。而且,你是否希望在函数中使用的全局变量也被早期绑定“固定”呢?这几乎没有任何用处,但对于全局变量和自由变量具有相同名称的情况,这将完全不兼容。 - Alex Martelli