读/写 Python 闭包

发布于 2024-08-17 02:14:42 字数 786 浏览 7 评论 0原文

闭包是一个非常有用的语言功能。它们让我们可以做一些聪明的事情,否则需要大量代码,并且通常使我们能够编写更优雅、更清晰的代码。在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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

丘比特射中我 2024-08-24 02:14:42

扩展 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 更为惯用。

To expand on Ignacio's answer:

def counter():
    count = 0
    def c():
        nonlocal count
        count += 1
        return count
    return c

x = counter()
print([x(),x(),x()])

gives [1,2,3] in Python 3; invocations of counter() give independent counters. Other solutions - especially using itertools/yield are more idiomatic.

夜还是长夜 2024-08-24 02:14:42

你可以这样做,它的工作方式或多或少是相同的:

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。)

无论如何,

You could do this and it would work more or less the same way:

class counter(object):
    def __init__(self, count=0):
        self.count = count
    def __call__(self):
        self.count += 1
        return self.count    

Or, a bit of a hack:

def counter():
    count = [0]
    def incr(n):
        n[0] += 1
        return n[0]
    return lambda: incr(count)

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:

new = [x for x in old]

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 a for loop has the same behavior. The only way to avoid this is to use map.)

Anyway, </rant>

-残月青衣踏尘吟 2024-08-24 02:14:42

3.x 中的 nonlocal 应该可以解决这个问题。

nonlocal in 3.x should remedy this.

倥絔 2024-08-24 02:14:42

我会使用生成器:

>>> 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 邮件列表上的次数
和其他地方,并导致了延长
讨论和许多方法建议
以消除此限制。这个政治公众号
总结了各种替代方案
一起建议的
优点和缺点
每个都已被提及。

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

I would use a generator:

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

EDIT: I believe the ultimate answer to your question is PEP-3104:

In most languages that support nested
scopes, code can refer to or rebind
(assign to) any name in the nearest
enclosing scope. Currently, Python
code can refer to a name in any
enclosing scope, but it can only
rebind names in two scopes: the local
scope (by simple assignment) or the
module-global scope (using a global
declaration).

This limitation has been raised many
times on the Python-Dev mailing list
and elsewhere, and has led to extended
discussion and many proposals for ways
to remove this limitation. This PEP
summarizes the various alternatives
that have been suggested, together
with advantages and disadvantages that
have been mentioned for each.

Before version 2.1, Python's treatment
of scopes resembled that of standard
C: within a file there were only two
levels of scope, global and local. In
C, this is a natural consequence of
the fact that function definitions
cannot be nested. But in Python,
though functions are usually defined
at the top level, a function
definition can be executed anywhere.
This gave Python the syntactic
appearance of nested scoping without
the semantics, and yielded
inconsistencies that were surprising
to some programmers -- for example, a
recursive function that worked at the
top level would cease to work when
moved inside another function, because
the recursive function's own name
would no longer be visible in its
body's scope. This violates the
intuition that a function should
behave consistently when placed in
different contexts.

心安伴我暖 2024-08-24 02:14:42

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

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

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

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

Functions can also have attributes, so this would work, too:

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

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).

っ〆星空下的拥抱 2024-08-24 02:14:42

官方 Python 教程 非常彻底地解释了这种行为以及 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:

A special quirk of Python is that – if
no global statement is in effect –
assignments to names always go into
the innermost scope.

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 :-)

别在捏我脸啦 2024-08-24 02:14:42

它们并不是只读的,而是您意识到的范围更加严格。如果您不能在 Python 3+ 中使用非本地,那么您至少可以使用显式作用域。 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

需要做更多的工作才能为 count 变量提供更严格的作用域,而不是使用伪全局模块变量(仍然是 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

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:

>>> 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

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):

>>> 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
爱给你人给你 2024-08-24 02:14:42

扩展 sdcvvc 将参数传递给闭包的答案。

def counter():
    count = 0
    def c(delta=1):
        nonlocal count
        count += delta
        return count
    return c

x = counter()
print([x(), x(100), x(-99)])

线程安全版本:

import threading

def counter():
    count = 0
    _lock = threading.Lock()
    def c(delta=1):
        nonlocal count
        with _lock:
            count += delta
            return count
    return c

To expand on sdcvvc's answer for passing param to closure.

def counter():
    count = 0
    def c(delta=1):
        nonlocal count
        count += delta
        return count
    return c

x = counter()
print([x(), x(100), x(-99)])

Thread-safe version:

import threading

def counter():
    count = 0
    _lock = threading.Lock()
    def c(delta=1):
        nonlocal count
        with _lock:
            count += delta
            return count
    return c
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文