与语言 X 闭包相比,Python 中的闭包有哪些限制?

What limitations have closures in Python compared to language X closures?

提问人:jfs 提问时间:9/27/2008 最后编辑:Communityjfs 更新时间:6/3/2014 访问量:8664

问:

其中 X 是任何支持某种闭包的编程语言(C#、Javascript、Lisp、Perl、Ruby 等)。

Python 中的闭包中提到了一些限制(与 Ruby 的闭包相比),但这篇文章已经很旧了,许多限制在现代 Python 中不再存在。

查看具体限制的代码示例会很棒。

相关问题

Python 闭包

评论


答:

6赞 John Millikin 9/27/2008 #1

我看到人们在使用 Python 时遇到的唯一困难是,当他们试图将变量重赋等非功能性功能与闭包混合在一起时,当这不起作用时会感到惊讶:

def outer ():
    x = 1
    def inner ():
        print x
        x = 2
    return inner
outer () ()

通常,只要指出一个函数有自己的局部变量就足以阻止这种愚蠢。

评论

2赞 John Millikin 9/27/2008
@Moe 谢谢 ;@JF没错。闭包的工作方式与任何其他函数一样,但出于某种原因,人们认为它们在分配变量时应该发挥魔力。
0赞 jfs 9/27/2008
我的评论太大了。我已将其作为答案发布。
45赞 Thomas Wouters 9/27/2008 #2

目前,最重要的限制是不能赋值给外域变量。换言之,闭包是只读的:

>>> def outer(x): 
...     def inner_reads():
...         # Will return outer's 'x'.
...         return x
...     def inner_writes(y):
...         # Will assign to a local 'x', not the outer 'x'
...         x = y
...     def inner_error(y):
...         # Will produce an error: 'x' is local because of the assignment,
...         # but we use it before it is assigned to.
...         tmp = x
...         x = y
...         return tmp
...     return inner_reads, inner_writes, inner_error
... 
>>> inner_reads, inner_writes, inner_error = outer(5)
>>> inner_reads()
5
>>> inner_writes(10)
>>> inner_reads()
5
>>> inner_error(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in inner_error
UnboundLocalError: local variable 'x' referenced before assignment

除非另有声明,否则在本地作用域(函数)中分配给的名称始终是本地名称。虽然有“全局”声明来声明一个变量全局,即使它被赋值给,但对于封闭的变量还没有这样的声明。在 Python 3.0 中,有(将有)“非本地”声明可以做到这一点。

同时,您可以使用可变容器类型来解决此限制:

>>> def outer(x):
...     x = [x]
...     def inner_reads():
...         # Will return outer's x's first (and only) element.
...         return x[0]
...     def inner_writes(y):
...         # Will look up outer's x, then mutate it.      
...         x[0] = y
...     def inner_error(y):
...         # Will now work, because 'x' is not assigned to, just referenced.
...         tmp = x[0]
...         x[0] = y
...         return tmp
...     return inner_reads, inner_writes, inner_error
... 
>>> inner_reads, inner_writes, inner_error = outer(5)
>>> inner_reads()
5
>>> inner_writes(10)
>>> inner_reads()
10
>>> inner_error(15)
10
>>> inner_reads()
15

评论

8赞 jfs 9/27/2008
nonlocal正如你所提到的,解决了这个问题。一个内部类也可以解决它(但带有列表的版本更简洁)。__call__
2赞 jfs 9/27/2008 #3

@John米利金

def outer():
    x = 1 # local to `outer()`

    def inner():
        x = 2     # local to `inner()`
        print(x)
        x = 3
        return x

    def inner2():
        nonlocal x
        print(x)  # local to `outer()`
        x = 4     # change `x`, it is not local to `inner2()`
        return x

    x = 5         # local to `outer()`
    return (inner, inner2)

for inner in outer():
    print(inner()) 

# -> 2 3 5 4
5赞 Kevin Little 9/27/2008 #4

在 Python 3 中通过非本地语句修复:

该语句使列出的标识符引用最近封闭范围(不包括全局变量)中先前绑定的变量。这很重要,因为绑定的默认行为是首先搜索本地命名空间。该语句允许封装的代码重新绑定全局(模块)范围之外的局部范围之外的变量。nonlocal

评论

0赞 jfs 9/27/2008
我的评论太大了。我已将其作为答案发布。
0赞 interstar 10/12/2008
嗯......据推测,非局部变量在闭包的所有实例之间共享?让它更像一个静态/类变量?
2赞 jfs 9/27/2008 #5

注释 @Kevin Little 的答案以包含代码示例

nonlocal在 python3.0 上不能完全解决这个问题:

x = 0 # global x
def outer():
    x = 1 # local to `outer`
    def inner():
        global x
        x = 2 # change global
        print(x) 
        x = 3 # change global
        return x
    def inner2():
##        nonlocal x # can't use `nonlocal` here
        print(x)     # prints global
##        x = 4      # can't change `x` here
        return x
    x = 5
    return (inner, inner2)

for inner in outer():
    print(inner())
# -> 2 3 3 3

另一方面:

x = 0
def outer():
    x = 1 # local to `outer`
    def inner():
##        global x
        x = 2
        print(x) # local to `inner` 
        x = 3 
        return x
    def inner2():
        nonlocal x
        print(x)
        x = 4  # local to `outer`
        return x
    x = 5
    return (inner, inner2)

for inner in outer():
    print(inner())
# -> 2 3 5 4

它适用于 python3.1-3.3

评论

0赞 l4mpi 9/28/2012
我知道这是一个非常古老的答案,但对于 python3.2 来说,这显然是错误的 - 您可以在第一个示例中完全使用非本地并获得预期的结果。我没有 python3.0 版本来检查它当时是否不同,但如果是,我会认为这是一个错误,因为 PEP 没有说明它应该不同。唯一(明显的)限制是您不能在同一范围内使用 AND。global xnonlocal x
0赞 jfs 9/28/2012
@l4mpi:如果您取消注释,它会在 python3.0 上生成。但它适用于 python3.1-3.3SyntaxError: no binding for nonlocal 'x' foundnonlocal
0赞 l4mpi 9/28/2012
啊,谢谢你的回答。现在这似乎更像是一个错误,尽管我无法在 3.1 发行说明中找到错误报告或注释......
-2赞 Jay O 6/13/2009 #6

在 3.0 之前,更好的解决方法是将变量作为默认参数包含在随附的函数定义中:

def f()
    x = 5
    def g(y, z, x=x):
        x = x + 1

评论

4赞 jfs 6/13/2009
outer 的值将始终为 。x5
6赞 mykhal 8/9/2011 #7

与 Javascript 闭包相比,Python 闭包的一个局限性(或“限制”)是它不能用于有效的数据隐藏

Java脚本

var mksecretmaker = function(){
    var secrets = [];
    var mksecret = function() {
        secrets.push(Math.random())
    }
    return mksecret
}
var secretmaker = mksecretmaker();
secretmaker(); secretmaker()
// privately generated secret number list
// is practically inaccessible

import random
def mksecretmaker():
    secrets = []
    def mksecret():
        secrets.append(random.random())
    return mksecret

secretmaker = mksecretmaker()
secretmaker(); secretmaker()
# "secrets" are easily accessible,
# it's difficult to hide something in Python:
secretmaker.__closure__[0].cell_contents # -> e.g. [0.680752847190161, 0.9068475951742101]

评论

3赞 yati sagade 7/4/2013
即使使用标准的 OO 数据隐藏,您也可以使用 obj._Class__private_variable 来获取 Python 类的__private_variable。数据隐藏的目的是提供抽象,而不是安全性,如果你的客户端代码一心想破坏,那就四处乱逛。
1赞 Kuba hasn't forgotten Monica 12/15/2021
“易于访问” - 如果您需要真正晦涩难懂的语法,那么它就不容易访问。C++ 中的私有成员同样可以通过指针操作和目标 ABI 中的对象布局知识“轻松访问”。信息隐藏并不是要让有决心的人无法访问事情。这是关于使内部隐藏起来,以免随意无意中使用。就这样。