读/写Python闭包

闭包是一个令人难以置信的有用的语言function。 他们让我们做聪明的事情,否则会占用大量的代码,并且经常使我们能够编写更优雅更清晰的代码。 在Python 2.x中,闭包variables名称不能被反弹; 也就是说,在另一个词法范围内定义的函数不能像some_var = 'changed!'那样做some_var = 'changed!' 对于局部范围以外的variables。 有人可以解释为什么吗? 有些情况下,我想创build一个闭包,重新绑定外部variables的variables,但这是不可能的。 我认识到几乎在所有情况下(如果不是全部),这种行为可以通过类来实现,但是通常不是那么干净或者优雅。 为什么我不能用闭包来实现呢?

这是一个重新绑定closures的例子:

 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 

要扩大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()调用给出独立的计数器。 其他解决scheme – 特别是使用itertools / yield更加惯用。

你可以做到这一点,它会或多或less地以相同的方式工作:

 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) 

我会用第一个解决scheme。

编辑:这就是我不读文本的大博客。

无论如何,Pythonclosures的原因相当有限,因为“因为Guido感觉像。 Python是在OO的鼎盛时期在90年代初devise的。 在人们想要的语言function列表中,closures程度相当低。 像一streamfunction,封闭和其他function的function性思想进入主stream的stream行趋势,像Python这样的语言不得不加以解决,所以它们的使用可能有些尴尬,因为这不是语言的devise目的。

<rant on="Python scoping">

此外,Python(2.x)还有一些奇怪的想法(在我看来),这些想法会影响closures的合理实现。 这总是困扰我:

 new = [x for x in old] 

留给我们的是在我们使用的范围内定义的名称x ,因为它(在我看来)是一个概念上较小的范围。 (尽pipePython得到了一致性的点,但是用for循环做同样的事情也是一样的,唯一的办法就是使用map 。)

无论如何, </rant>

在3.x的nonlocal应该补救这一点。

我会使用一个生成器:

 >>> 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总结了已经提出的各种备选scheme,以及每个scheme提到的优点和缺点。

在版本2.1之前,Python对范围的处理类似于标准C的处理:在一个文件中,只有两层范围,全局和本地。 在C中,这是事实的一个自然结果,即函数定义不能嵌套。 但是在Python中,尽pipe函数通常定义在顶层,但函数定义可以在任何地方执行。 这给Python提供了没有语义的嵌套范围的语法外观,并且产生了一些令程序员惊讶的不一致 – 例如,在顶层工作的recursion函数在移入另一个函数时将停止工作,因为recursion函数的自己的名字将不再在其身体范围内可见。 这违背了一个函数在不同环境中应该一致地performance的直觉。

函数也可以有属性,所以这也可以工作:

 def counter(): def c(): while True: yield c.count c.count += 1 c.count = 0 return c 

不过,在这个具体的例子中,我会使用jbochibuild议的生成器。

至于为什么 ,我不能肯定地说,但我想这不是一个明确的deviseselect,而是Python的有时是奇怪的范围规则的残余(特别是其范围规则有点奇怪的演变)。

这个行为是相当彻底地解释了官方的Python教程以及Python的执行模型 。 特别是从教程:

Python的一个特别的问题是,如果没有全局语句生效,对名称的分配总是进入最内层的范围。

然而,这并没有说明为什么它以这种方式行事。

一些更多的信息来自PEP 3104 ,它试图解决Python 3.0的这种情况。
在那里,你可以看到,这是因为在某个时间点,它被认为是最好的解决scheme,而不是介绍经典的静态嵌套作用域(请参阅Re:作用域(是Re:Lambda绑定解决?) )。

也就是说,我也有自己的解释。
Python将名称空间实现为字典; 当一个variables的查找失败的内部,然后它尝试在外部等,直到它到达内build。
然而, 绑定一个variables是一个完全不同的东西,因为你需要指定一个特定的名称空间 – 它总是最内层的(除非你设置了“全局”标志,这意味着它总是全局名称空间)。
最后,用于查找和绑定variables的不同algorithm是闭包在Python中只读的原因。
但是,这只是我的猜测:-)

这不是说它们是只读的,就是你意识到的范围更严格。 如果你不能在Python 3+中使用nonlocal,那么你至less可以使用显式的范围。 Python 2.6.1,在模块级显式范围:

 >>> def counter(): ... sys.modules[__name__].count = 0 ... def c(): ... sys.modules[__name__].count += 1 ... return sys.modules[__name__].count ... sys.modules[__name__].c = c ... >>> counter() >>> c() 1 >>> c() 2 >>> c() 3 

需要做更多的工作,使countvariables的范围更加有限,而不是使用伪全局模块variables(仍然是Python 2.6.1):

 >>> def counter(): ... class c(): ... def __init__(self): ... self.count = 0 ... cinstance = c() ... def iter(): ... cinstance.count += 1 ... return cinstance.count ... return iter ... >>> c = counter() >>> c() 1 >>> c() 2 >>> c() 3 >>> d = counter() >>> d() 1 >>> c() 4 >>> d() 2