读/写Python闭包

问题描述 投票:34回答:8

关闭是一种非常有用的语言功能。他们让我们做一些聪明的事情,这些事情本来需要很多代码,并且常常使我们能够编写更优雅,更清晰的代码。在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
python language-design closures readonly
8个回答
29
投票
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更惯用。


22
投票
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之类的语言不得不加以坚持,因此它们的使用可能有点尴尬,因为这不是该语言的设计目的。

<rant on="Python scoping">

此外,Python(2.x)在范围界定方面有很多奇怪的想法(在我看来),这些范围界定会干扰合理的闭包实现。它总是让我感到困扰:

new = [x for x in old]

在我们使用的范围内给我们定义了名称x,因为(我认为)它是概念上较小的范围。 (尽管Python获得了一致性点,因为使用for循环执行相同的操作具有相同的行为。避免这种情况的唯一方法是使用map。)

无论如何,</rant>

17
投票

13
投票
>>> def counter(): count = 0 while True: count += 1 yield(count) >>> c = counter() >>> c.next() 1 >>> c.next() 2 >>> c.next() 3

EDIT:我相信您的问题的最终答案是PEP-3104

在大多数支持嵌套的语言中范围,代码可以引用或重新绑定(分配给)最近的任何名称封闭范围。目前,Python代码可以引用任何名称封闭范围,但只能在两个范围内重新绑定名称:本地范围(通过简单分配)或模块全局范围(使用全局范围声明)。

此限制已提出很多Python-Dev邮件列表上的时间和其他地方,并导致扩展讨论和许多方法建议消除此限制。这个PEP总结了各种选择一起被建议优点和缺点是已经提到了每个。

在2.1版之前,Python的处理方式范围类似于标准C:一个文件中只有两个范围级别,全球和本地。在C,这是自然的结果函数定义的事实不能嵌套。但是在Python中尽管通常定义功能在顶层,一个功能定义可以在任何地方执行。这给了Python语法没有的嵌套作用域的外观语义,并产生令人惊讶的不一致对于某些程序员-例如,一个递归函数顶级将停止工作,当移动到另一个函数中,因为递归函数自己的名字将不再在其中可见身体的范围。这违反了函数应该具有的直觉放置时表现一致不同的上下文。


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

但是,在此特定示例中,我将使用jbochi建议的生成器。

至于[[为什么,我不能肯定地说,但我想这不是一个明确的设计选择,而是Python有时奇怪的作用域规则(尤其是其作用域的某种奇怪的演变)的残余。规则)。

6
投票
Python的一个特殊怪癖是–如果没有有效的全球声明–名称分配总是进入最内层的范围。

但是,这并没有说明

为什么的行为。

[更多信息来自PEP 3104,它试图解决Python 3.0的这种情况。在那里,您可以看到它是这种方式,因为在某个时间点,它被视为是最佳解决方案,而不是引入经典的静态嵌套范围(请参见Re: Scoping (was Re: Lambda binding solved?))。

也就是说,我也有自己的解释。Python将名称空间实现为字典;当变量的[[lookup在内部失败时,则在外部尝试,依此类推,直到到达内置变量为止。但是,binding变量是完全不同的东西,因为您需要指定一个特定的名称空间-它始终是最里面的名称空间(除非您设置“ global”标志,这意味着它始终是全局名称空间) 。最终,用于查找和绑定变量的不同算法是闭包在Python中为只读的原因。但是,再次,这只是我的推测:-)


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

0
投票
import threading def counter(): count = 0 _lock = threading.Lock() def c(delta=1): nonlocal count with _lock: count += delta return count return c
© www.soinside.com 2019 - 2024. All rights reserved.