为什么Python嵌套函数不叫闭包?
我见过并使用过Python中的嵌套函数,它们符合闭包的定义。那么为什么它们叫“嵌套函数”,而不是“闭包”呢?
嵌套函数是不是闭包,因为它们不被外部世界使用?
更新:我在阅读关于闭包的内容时,想到这个概念在Python中的应用。我搜索了一下,找到了下面评论中提到的文章,但我对那篇文章的解释完全理解不了,所以我才问这个问题。
10 个回答
Python 对闭包的支持比较弱。为了让你明白这一点,我们可以看看用 JavaScript 实现的一个计数器的例子:
function initCounter(){
var x = 0;
function counter () {
x += 1;
console.log(x);
};
return counter;
}
count = initCounter();
count(); //Prints 1
count(); //Prints 2
count(); //Prints 3
闭包的写法很优雅,因为它让函数可以有“内部记忆”。但是在 Python 2.7 中,这种写法是行不通的。如果你尝试这样做:
def initCounter():
x = 0;
def counter ():
x += 1 ##Error, x not defined
print x
return counter
count = initCounter();
count(); ##Error
count();
count();
你会收到一个错误提示,告诉你 x 没有定义。但为什么会这样呢?因为其他人已经证明你可以打印它。这是因为 Python 管理函数变量的作用域的方式。虽然内部函数可以读取外部函数的变量,但它不能写入这些变量。
这真的很可惜。不过,即使只有只读的闭包,你也可以实现函数装饰器模式,而 Python 也为此提供了语法糖。
更新
正如有人指出的,确实有一些方法可以处理 Python 的作用域限制,我会介绍一些。
1. 使用 global
关键字(一般不推荐)。
2. 在 Python 3.x 中,使用nonlocal
关键字(这是 @unutbu 和 @leewz 提出的建议)。
3. 定义一个简单的可修改类 Object
class Object(object):
pass
并在 initCounter
中创建一个 Object scope
来存储变量。
def initCounter ():
scope = Object()
scope.x = 0
def counter():
scope.x += 1
print scope.x
return counter
由于 scope
实际上只是一个引用,对它的字段进行的操作并不会真正修改 scope
本身,因此不会出现错误。
4. 另一种方法,如 @unutbu 指出的,可以将每个变量定义为一个数组(x = [0]
),然后修改它的第一个元素(x[0] += 1
)。同样不会出现错误,因为 x
本身并没有被修改。
5. 根据 @raxacoricofallapatorius 的建议,你可以将 x
设为 counter
的一个属性。
def initCounter ():
def counter():
counter.x += 1
print counter.x
counter.x = 0
return counter
这个问题已经被 aaronasterling 回答过了
不过,可能有人会对变量是如何存储的感兴趣。
在进入代码片段之前:
闭包是从其外部环境中继承变量的函数。当你把一个函数作为回调传递给另一个进行输入输出操作的函数时,这个回调函数会在稍后被调用,而这个函数几乎是“魔法般”地记住了它被声明时的上下文,以及在那个上下文中可用的所有变量。
如果一个函数不使用自由变量,它就不会形成闭包。
如果有另一个内部层级使用自由变量——所有之前的层级都会保存词法环境(最后有示例)。
在python < 3.X中,函数属性
func_closure
或在python > 3.X中__closure__
保存自由变量。每个 Python 函数都有闭包属性,但如果没有自由变量,它就是空的。
示例:闭包属性但没有内容,因为没有自由变量。
>>> def foo():
... def fii():
... pass
... return fii
...
>>> f = foo()
>>> f.func_closure
>>> 'func_closure' in dir(f)
True
>>>
注意:自由变量 是创建闭包的必要条件。
我将使用上面的代码片段来解释:
>>> def make_printer(msg):
... def printer():
... print msg
... return printer
...
>>> printer = make_printer('Foo!')
>>> printer() #Output: Foo!
所有 Python 函数都有一个闭包属性,所以让我们检查与闭包函数相关的外部变量。
这里是函数 printer
的属性 func_closure
>>> 'func_closure' in dir(printer)
True
>>> printer.func_closure
(<cell at 0x108154c90: str object at 0x108151de0>,)
>>>
closure
属性返回一个包含定义在外部作用域中的变量详细信息的元组。
func_closure 的第一个元素可能是 None,也可能是一个包含函数自由变量绑定的单元元组,并且它是只读的。
>>> dir(printer.func_closure[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__',
'__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>>
在上面的输出中,你可以看到 cell_contents
,让我们看看它存储了什么:
>>> printer.func_closure[0].cell_contents
'Foo!'
>>> type(printer.func_closure[0].cell_contents)
<type 'str'>
>>>
所以,当我们调用函数 printer()
时,它访问了存储在 cell_contents
中的值。这就是我们得到输出 'Foo!' 的原因。
我将再次使用上面的代码片段,做一些修改:
>>> def make_printer(msg):
... def printer():
... pass
... return printer
...
>>> printer = make_printer('Foo!')
>>> printer.func_closure
>>>
在上面的代码片段中,我没有在 printer 函数内部打印 msg,所以它没有创建任何自由变量。由于没有自由变量,闭包内部也不会有内容。这正是我们在上面看到的。
现在我将解释另一个不同的代码片段,以清楚地说明 自由变量
和 闭包
的关系:
>>> def outer(x):
... def intermediate(y):
... free = 'free'
... def inner(z):
... return '%s %s %s %s' % (x, y, free, z)
... return inner
... return intermediate
...
>>> outer('I')('am')('variable')
'I am free variable'
>>>
>>> inter = outer('I')
>>> inter.func_closure
(<cell at 0x10c989130: str object at 0x10c831b98>,)
>>> inter.func_closure[0].cell_contents
'I'
>>> inn = inter('am')
所以,我们看到 func_closure
属性是一个闭包 单元 的元组,我们可以明确地引用它们及其内容——一个单元有属性 "cell_contents"
>>> inn.func_closure
(<cell at 0x10c9807c0: str object at 0x10c9b0990>,
<cell at 0x10c980f68: str object at 0x10c9eaf30>,
<cell at 0x10c989130: str object at 0x10c831b98>)
>>> for i in inn.func_closure:
... print i.cell_contents
...
free
am
I
>>>
在这里,当我们调用 inn
时,它会引用所有保存的自由变量,所以我们得到了 我是一自由变量
>>> inn('variable')
'I am free variable'
>>>
闭包是指一个函数可以访问它外部作用域中已经执行完的局部变量。
def make_printer(msg):
def printer():
print(msg)
return printer
printer = make_printer('Foo!')
printer()
当调用 make_printer
时,会在栈上创建一个新的框架,这个框架里包含了 printer
函数的编译代码作为常量,以及 msg
的值作为局部变量。然后,它会创建并返回这个函数。因为 printer
函数引用了 msg
变量,所以在 make_printer
函数返回后,msg
仍然会被保留。
所以,如果你的嵌套函数没有:
- 访问外部作用域的局部变量,
- 在外部作用域执行时访问这些变量,
那么它们就不是闭包。
下面是一个不是闭包的嵌套函数的例子。
def make_printer(msg):
def printer(msg=msg):
print(msg)
return printer
printer = make_printer("Foo!")
printer() #Output: Foo!
在这里,我们把一个值绑定到参数的默认值上。这发生在创建 printer
函数的时候,因此在 make_printer
返回后,不需要保持对 printer
外部的 msg
值的引用。msg
在这个上下文中只是 printer
函数的一个普通局部变量。