读/写 Python 闭包
闭包是一个非常有用的语言功能。它们让我们可以做一些聪明的事情,否则需要大量代码,并且通常使我们能够编写更优雅、更清晰的代码。在Python 2.x中,闭包变量名不能被反弹;也就是说,在另一个词法作用域内定义的函数不能对其本地作用域之外的变量执行类似 some_var = 'changed!'
的操作。有人可以解释这是为什么吗?在某些情况下,我想创建一个闭包来重新绑定外部作用域中的变量,但这是不可能的。我意识到在几乎所有情况下(如果不是全部),这种行为都可以通过类来实现,但它通常不那么干净或优雅。为什么我不能用闭包来做到这一点?
这是重新绑定闭包的示例:
def counter():
count = 0
def c():
count += 1
return count
return c
这是您调用它时的当前行为:
>>> c()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in c
UnboundLocalError: local variable 'count' referenced before assignment
我希望它做的是:
>>> c()
1
>>> c()
2
>>> c()
3
Closures are an incredibly useful language feature. They let us do clever things that would otherwise take a lot of code, and often enable us to write code that is more elegant and more clear. In Python 2.x, closures variable names cannot be rebound; that is, a function defined inside another lexical scope cannot do something like some_var = 'changed!'
for variables outside of its local scope. Can someone explain why that is? There have been situations in which I would like to create a closure that rebinds variables in the outer scope, but it wasn't possible. I realize that in almost all cases (if not all of them), this behavior can be achieved with classes, but it is often not as clean or as elegant. Why can't I do it with a closure?
Here is an example of a rebinding closure:
def counter():
count = 0
def c():
count += 1
return count
return c
This is the current behavior when you call it:
>>> c()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in c
UnboundLocalError: local variable 'count' referenced before assignment
What I'd like it to do instead is this:
>>> c()
1
>>> c()
2
>>> c()
3
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
扩展 Ignacio 的答案:
在 Python 3 中给出 [1,2,3];
counter()
的调用给出独立的计数器。其他解决方案 - 特别是使用itertools
/yield
更为惯用。To expand on Ignacio's answer:
gives [1,2,3] in Python 3; invocations of
counter()
give independent counters. Other solutions - especially usingitertools
/yield
are more idiomatic.你可以这样做,它的工作方式或多或少是相同的:
或者,有点黑客:
我会选择第一个解决方案。
编辑:这就是我不阅读大文本博客所得到的结果。
无论如何,Python 闭包相当有限的原因是“因为 Guido 喜欢它”。 Python 设计于 90 年代初,正值 OO 的鼎盛时期。在人们想要的语言功能列表中,闭包的排名相当靠后。随着一等函数、闭包和其他东西等函数式思想进入主流流行,像 Python 这样的语言不得不附加它们,因此它们的使用可能有点尴尬,因为这不是该语言的设计目的。
此外,Python (2.x) 对于范围界定有相当奇怪的(在我看来)想法,它会干扰闭包的合理实现等。这总是让我困扰:
让我们在使用它的范围中定义名称
x
,因为(在我看来)它在概念上是一个较小的范围。 (尽管 Python 因一致性而得分,因为使用for
循环执行相同的操作具有相同的行为。避免这种情况的唯一方法是使用map
。)无论如何,
You could do this and it would work more or less the same way:
Or, a bit of a hack:
I'd go with the first solution.
EDIT: That's what I get for not reading the big blog of text.
Anyway, the reason Python closures are rather limited is "because Guido felt like it." Python was designed in the early 90s, in the heyday of OO. Closures were rather low on the list of language features people wanted. As functional ideas like first class functions, closures, and other things make their way into mainstream popularity, languages like Python have had to tack them on, so their use may a bit awkward, because that's not what the language was designed for.
<rant on="Python scoping">
Also, Python (2.x) has rather odd (in my opinion) ideas about scoping that interferes with a sane implementation of closures, among other things. It always bothers me that this:
Leaves us with the name
x
defined in the scope we used it in, as it is (in my opinion) a conceptually smaller scope. (Though Python gets points for consistency, as doing the same thing with afor
loop has the same behavior. The only way to avoid this is to usemap
.)Anyway,
</rant>
3.x 中的
nonlocal
应该可以解决这个问题。nonlocal
in 3.x should remedy this.我会使用生成器:
编辑:我相信您问题的最终答案是PEP-3104:
I would use a generator:
EDIT: I believe the ultimate answer to your question is PEP-3104:
函数也可以有属性,所以这也可以:
但是,在这个特定的示例中,我将使用 jbochi 建议的生成器。
至于为什么,我不能肯定地说,但我想这不是一个明确的设计选择,而是Python有时奇怪的作用域规则的残余(尤其是它的有点奇怪的演变)范围规则)。
Functions can also have attributes, so this would work, too:
However, in this specific example, I'd use a generator as suggested by jbochi.
As for why, I can't say for sure, but I imagine it's not an explicit design choice, but rather a remnant of Python's sometimes-odd scoping rules (and especially the somewhat-odd evolution of its scoping rules).
官方 Python 教程 非常彻底地解释了这种行为以及 Python 执行模型。特别是,从教程中:
然而,这并没有说明为什么它会以这种方式表现。
更多信息来自 PEP 3104,它试图解决这种情况Python 3.0。
在那里,您可以看到它是这样的,因为在某个时间点,它被视为最佳解决方案,而不是引入经典的静态嵌套范围(请参阅 回复:范围界定(回复:Lambda 绑定已解决吗?))。
话虽如此,我也有自己的解释。
Python 将命名空间实现为字典;当变量在内部查找失败时,它会在外部尝试,依此类推,直到到达内置变量。
然而,绑定变量是完全不同的东西,因为您需要指定一个特定的名称空间 - 它始终是最里面的名称空间(除非您设置“全局”标志,这意味着它始终是最里面的名称空间)全局命名空间)。
最终,用于查找和绑定变量的不同算法是 Python 中闭包为只读的原因。
但是,这只是我的猜测:-)
This behavior is quite thoroughly explained the official Python tutorial as well as in the Python execution model. In particular, from the tutorial:
However, this does not say anything about why it behaves in this way.
Some more information comes from PEP 3104, that tries to tackle this situation for Python 3.0.
There, you can see that it is this way because at a certain point in time, it was seen as the best solution instead of introducing classic static nested scopes (see Re: Scoping (was Re: Lambda binding solved?)).
That said, I have also my own interpretation.
Python implements namespaces as dictionaries; when a lookup for a variable fails in the inner, then it tries in the outer and so on, until it reaches the builtins.
However, binding a variable is a completely different stuff, because you need to specify a particular namespace - that it is always the innermost one (unless you set the "global" flag, that means it is always the global namespace).
Eventually, the different algorithms used for looking up and binding variables are the reason for closures to be read-only in Python.
But, again, this is just my speculation :-)
它们并不是只读的,而是您意识到的范围更加严格。如果您不能在 Python 3+ 中使用非本地,那么您至少可以使用显式作用域。 Python 2.6.1,在模块级别具有显式作用域:
需要做更多的工作才能为 count 变量提供更严格的作用域,而不是使用伪全局模块变量(仍然是 Python 2.6.1):
It is not that they are read-only, as much as the scope is more strict that you realize. If you can't
nonlocal
in Python 3+, then you can at least use explicit scoping. Python 2.6.1, with explicit scoping at the module level:A little more work is required to have a more restricted scope for the count variable, instead of using a pseudo-global module variable (still Python 2.6.1):
扩展 sdcvvc 将参数传递给闭包的答案。
线程安全版本:
To expand on sdcvvc's answer for passing param to closure.
Thread-safe version: