如何实现闭包? [英] How are closures implemented?

查看:80
本文介绍了如何实现闭包?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

学习Python,第4版。提到:

"Learning Python, 4th Ed." mentions that:


当嵌套函数
稍后调用时,查找包含范围变量。

the enclosing scope variable is looked up when the nested functions are later called..

但是,我认为当一个函数退出时,所有的局部引用都会消失。

However, I thought that when a function exits, all of its local references disappear.

def makeActions():
    acts = []
    for i in range(5): # Tries to remember each i
        acts.append(lambda x: i ** x) # All remember same last i!
return acts

makeActions()[n] 对于每个 n 是相同的,因为变量 i 在某种程度上在调用时查找。 Python如何查找这个变量?应该不是它根本不存在,因为makeActions已经退出了?为什么Python不执行代码直观地建议,并定义每个函数通过在循环中替换i与其当前值,因为循环正在运行?

makeActions()[n] is the same for every n because the variable i is somehow looked up at call time. How does Python look up this variable? Shouldn't it not exist at all because makeActions has already exited? Why doesn't Python do what the code intuitively suggests, and define each function by replacing i with its current value within the for loop as the loop is running?

从评论到THC4k:

我认为我误认为Python在内存中构建函数。我认为当遇到 def 或者 lambda 时,Python会生成所有必要的机器指令并将其保存在内存中的某处。现在我认为它更像是将函数作为一个文本字符串(并将其与封装所需的引用绑定),并在每次调用函数时重新解析它。

From comment to THC4k:
I think I was mistaken in the way Python builds functions in memory. I was thinking that when encountering a def or a lambda, Python would generate all the necessary machine instructions correlating to that function and save it somewhere in memory. Now I think it's more like Python saves the function as a text string (and bundles it with references needed for the closure) and re-parses it each time the function is called.

推荐答案

我认为很明显当你想到 i 作为名称不是某种。你的lambda函数执行类似take x:查找i的值,计算i ** x...所以当你实际运行该函数,它查找 i i 是 4

I think it's pretty obvious what happens when you think of i as a name not some sort of value. Your lambda function does something like "take x: look up the value of i, calculate i**x" ... so when you actually run the function, it looks up i just then so i is 4.

你也可以使用当前的数字,但你必须让Python绑定到另一个名字:

You can also use the current number, but you have to make Python bind it to another name:

def makeActions():
    def make_lambda( j ):
        return lambda x: j * x # the j here is still a name, but now it wont change anymore

    acts = []
    for i in range(5):
        # now you're pushing the current i as a value to another scope and 
        # bind it there, under a new name
        acts.append(make_lambda(i))
    return acts

,因为你经常得知一个变量和它的价值是相同的东西 - 这是真的,但只有在实际使用变量的语言。 Python没有变量,而是名称。

It might seem confusing, because you often get taught that a variable and it's value are the same thing -- which is true, but only in languages that actually use variables. Python has no variables, but names instead.

关于你的评论,实际上我可以更好地说明这一点:

About your comment, actually i can illustrate the point a bit better:

i = 5 
myList = [i, i, i] 
i = 6
print(myList) # myList is still [5, 5, 5].

你说你将i更改为6 ,这不是真正发生的: i = 6 表示我有一个值, 6 ,我想命名 i 。您已经使用 i 作为名称的事实对Python无关紧要,它只会重新分配名称,而不是更改其值(只适用于变量)。

You said you changed i to 6, that is not what actually happend: i=6 means "i have a value, 6 and i want to name it i". The fact that you already used i as a name matters nothing to Python, it will just reassign the name, not change it's value (that only works with variables).

你可以说在 myList = [i,i,i] ,无论值 i 当前指向(数字5)获得三个新名称: mylist [0],mylist [1] [2] 。这是调用函数时发生的相同的事情:参数被赋予新的名称。但这可能违反了关于列表的任何直觉...

You could say that in myList = [i, i, i], whatever value i currently points to (the number 5) gets three new names: mylist[0], mylist[1], mylist[2]. That's the same thing that happens when you call a function: The arguments are given new names. But that is probably going against any intuition about lists ...

这可以解释示例中的行为:您分配 mylist [0] = 5 mylist [1] = 5 mylist [2] = 5 当你重新分配 i 时,他们不会改变。如果 i 是可删除的,例如列表,则更改 i 将反映 myList ,因为你只有不同的名称为同一个值

This can explain the behavior in the example: You assign mylist[0]=5, mylist[1]=5, mylist[2]=5 - no wonder they don't change when you reassign the i. If i was something muteable, for example a list, then changing i would reflect on all entries in myList too, because you just have different names for the same value!

可以使用 = 左侧的 mylist [0] 证明它确实是一个名称。我喜欢调用 = assign name operator :它在左边有一个名称,在右边有一个表达式,然后计算表达式调用函数,查找名称后面的值),直到它有一个值,最后给出该值的名称。

The simple fact that you can use mylist[0] on the left hand of a = proves that it is indeed a name. I like to call = the assign name operator: It takes a name on the left, and a expression on the right, then evaluates the expression (call function, look up the values behind names) until it has a value and finally gives the name to the value. It does not change anything.

嗯,引用(和指针)只有当我们有某种类型的可寻址内存时才有意义。这些值存储在内存中的某个地方,引用引导你的地方。使用引用意味着去内存中的那个地方,并用它做一些事情。问题是这些概念的 none 被Python使用了!

Well, references (and pointers) only make sense when we have some sort of addressable memory. The values are stored somewhere in memory and references lead you that place. Using a reference means going to that place in memory and doing something with it. The problem is that none of these concepts are used by Python!

Python VM没有内存的概念 - 在空间和名称是连接到他们的小标签(由一个小红色字符串)。名称和值存在于不同的世界!

The Python VM has no concept of memory - values float somewhere in space and names are little tags connected to them (by a little red string). Names and values exist in separate worlds!

这在编译函数时有很大的不同。如果你有引用,你知道你引用的对象的内存位置。然后你可以简单地替换,然后引用这个位置。
另一方面,名称没有位置,所以你必须做的(在运行时)遵循那个小红色字符串,并使用任何在另一端。这是Python编译函数的方式:
在代码中有一个名称,它添加一个指令,将会找出这个名字代表什么。

This makes a big difference when you compile a function. If you have references, you know the memory location of the object you refer to. Then you can simply replace then reference with this location. Names on the other hand have no location, so what you have to do (during runtime) is follow that little red string and use whatever is on the other end. That is the way Python compiles functions: Where ever there is a name in the code, it adds a instruction that will figure out what that name stands for.

因此,基本上Python完全编译函数,但是名称在嵌套命名空间中被编译为查找,作为对内存的某种引用。

So basically Python does fully compile functions, but names are compiled as lookups in the nesting namespaces, not as some sort of reference to memory.

当您使用名称时,Python编译器将尝试找出它属于哪个命名空间的位置。这将导致一个指令从它找到的命名空间加载该名称。

When you use a name, the Python compiler will try to figure out where to which namespace it belongs to. This results in a instruction to load that name from the namespace it found.

这会带你回到原来的问题:在 lambda x:x ** i i 被编译为 makeActions 命名空间中的查找(因为 i 使用那里)。 Python不知道,也不关心它背后的价值(它甚至不必是一个有效的名称)。一个代码运行 i 获取在它的原始命名空间,并给出或多或少的期望值。

Which brings you back to your original problem: In lambda x:x**i, the i is compiled as a lookup in the makeActions namespace (because i was used there). Python has no idea, nor does it care about the value behind it (it does not even have to be a valid name). One that code runs the i gets looked up in it's original namespace and gives the more or less expected value.

这篇关于如何实现闭包?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆