闭包是一种非常有用的语言功能.他们让我们做一些聪明的事情,否则会占用大量代码,并且经常使我们能够编写更优雅,更清晰的代码.在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 "", line 1, in File " ", line 4, in c UnboundLocalError: local variable 'count' referenced before assignment
我想要它做的是:
>>> c() 1 >>> c() 2 >>> c() 3
sdcvvc.. 29
要扩展Ignacio的答案:
def counter(): count = 0 def c(): nonlocal count count += 1 return count return c x = counter() print([x(),x(),x()])
在Python 3中给出[1,2,3]; counter()
给予独立柜台的调用.其他解决方案 - 尤其是使用itertools
/ yield
更具惯用性.
要扩展Ignacio的答案:
def counter(): count = 0 def c(): nonlocal count count += 1 return count return c x = counter() print([x(),x(),x()])
在Python 3中给出[1,2,3]; counter()
给予独立柜台的调用.其他解决方案 - 尤其是使用itertools
/ yield
更具惯用性.
你可以这样做,它可以或多或少地以相同的方式工作:
class counter(object): def __init__(self, count=0): self.count = count def __call__(self): self.count += 1 return self.count
或者,有点黑客:
def counter(): count = [0] def incr(n): n[0] += 1 return n[0] return lambda: incr(count)
我会选择第一个解决方案.
编辑:这就是我没有阅读文本的大博客.
无论如何,Python封闭相当有限的原因是"因为Guido感觉就像这样." Python是在90年代早期,在OO的全盛时期设计的.关键词在人们想要的语言功能列表中相当低.随着像头等功能,闭包和其他东西的功能性思想进入主流流行,像Python这样的语言必须加以解决,因此它们的使用可能有点尴尬,因为这不是语言的设计目的.
此外,Python(2.x)还有一些奇怪的(在我看来)关于范围界定的想法,这些想法干扰了一个合理的闭包实现等等.这总是困扰我:
new = [x for x in old]
给我们留下我们x
在其使用的范围中定义的名称,因为它(在我看来)是概念上较小的范围.(虽然Python获得了一致性的要点,因为对for
循环执行相同的操作具有相同的行为.避免这种情况的唯一方法是使用map
.)
无论如何,
nonlocal
在3.x应该补救这个.
我会用一台发电机:
>>> def counter(): count = 0 while True: count += 1 yield(count) >>> c = counter() >>> c.next() 1 >>> c.next() 2 >>> c.next() 3
编辑:我相信你的问题的最终答案是PEP-3104:
在大多数支持嵌套作用域的语言中,代码可以引用或重新绑定(赋值)最近的封闭作用域中的任何名称.目前,Python代码可以引用任何封闭范围中的名称,但它只能重新绑定两个范围中的名称:本地范围(通过简单赋值)或模块全局范围(使用全局声明).
在Python-Dev邮件列表和其他地方已多次提出此限制,并引发了扩展讨论和许多方法来消除此限制.该PEP总结了已经提出的各种替代方案,以及针对每种方案提到的优点和缺点.
在2.1版之前,Python对范围的处理类似于标准C:在文件中只有两个范围,全局和本地.在C中,这是函数定义不能嵌套这一事实的自然结果.但是在Python中,虽然函数通常在顶层定义,但函数定义可以在任何地方执行.这给了Python没有语义的嵌套作用域的语法外观,并产生了令一些程序员感到惊讶的不一致性 - 例如,在顶层工作的递归函数在移动到另一个函数内时将停止工作,因为递归函数是自己的名字将不再在其正文范围内可见.
函数也可以有属性,所以这也可以工作:
def counter(): def c(): while True: yield c.count c.count += 1 c.count = 0 return c
但是,在这个具体的例子中,我会使用jbochi建议的生成器.
至于为什么,我不能肯定地说,但我认为它不是一个明确的设计选择,而是Python有时奇怪的范围规则的残余(尤其是其范围规则的有些奇怪的演变).
官方Python教程以及Python执行模型对此行为进行了彻底的解释.特别是,从教程:
Python的一个特殊之处在于 - 如果没有全局语句生效 - 对名称的赋值总是进入最内层范围.
但是,这并没有说明为什么它以这种方式表现.
更多信息来自PEP 3104,它试图解决Python 3.0的这种情况.
在那里,您可以看到它是这种方式,因为在某个时间点,它被视为最佳解决方案而不是引入经典的静态嵌套作用域(请参阅Re:作用域(Re:Lambda绑定已解决?)).
那就是说,我也有自己的解释.
Python将名称空间实现为字典; 当一个变量的查找在内部失败时,它会在外部尝试,依此类推,直到它到达内置函数.
但是,绑定变量是一个完全不同的东西,因为你需要指定一个特定的命名空间 - 它总是最内层的(除非你设置"global"标志,这意味着它总是全局命名空间).
最终,用于查找和绑定变量的不同算法是在Python中将闭包设置为只读的原因.
但是,再一次,这只是我的猜测:-)