我在Python中见过并使用过嵌套函数,它们与闭包的定义相匹配。那么为什么它们被称为“嵌套函数”而不是“闭包”呢?
嵌套函数不是闭包,因为它们不被外部使用吗?
更新:我正在阅读关于闭包的文章,它让我想到了关于Python的这个概念。我搜索了下面评论中有人提到的文章,但我不能完全理解文章中的解释,所以我才问这个问题。
我在Python中见过并使用过嵌套函数,它们与闭包的定义相匹配。那么为什么它们被称为“嵌套函数”而不是“闭包”呢?
嵌套函数不是闭包,因为它们不被外部使用吗?
更新:我正在阅读关于闭包的文章,它让我想到了关于Python的这个概念。我搜索了下面评论中有人提到的文章,但我不能完全理解文章中的解释,所以我才问这个问题。
当前回答
人们对什么是终结感到困惑。闭包不是内部函数。封闭的意义是封闭的行为。内部函数封闭在一个非局部变量上,这个变量叫做自由变量。
def counter_in(initial_value=0):
# initial_value is the free variable
def inc(increment=1):
nonlocal initial_value
initial_value += increment
print(initial_value)
return inc
当你调用counter_in()时,它将返回inc函数,该函数有一个自由变量initial_value。所以我们创建了一个CLOSURE。人们称inc为闭包函数,我认为这让人困惑,人们认为"内部函数是闭包"在现实中inc并不是一个闭包,因为它是闭包的一部分,为了方便起见,他们称它为闭包函数。
myClosingOverFunc=counter_in(2)
返回inc函数,该函数在自由变量initial_value上关闭。当你调用myClosingOverFunc
myClosingOverFunc()
它会输出2。
当python看到一个闭包系统存在时,它会创建一个名为CELL的新obj。这将只存储自由变量的名称,在本例中为initial_value。这个Cell obj将指向另一个存储initial_value值的对象。
在我们的例子中,外层函数和内部函数中的initial_value将指向这个单元格对象,而这个单元格对象将指向initial_value的值。
variable initial_value =====>> CELL ==========>> value of initial_value
所以当你调用counter_in时,它的作用域消失了,但这无关紧要。因为变量initial_value直接引用CELL对象。它间接引用initial_value的值。这就是为什么即使外部函数的作用域消失了,内部函数仍然可以访问自由变量
假设我想写一个函数,它接受一个函数作为参数,并返回这个函数被调用的次数。
def counter(fn):
# since cnt is a free var, python will create a cell and this cell will point to the value of cnt
# every time cnt changes, cell will be pointing to the new value
cnt = 0
def inner(*args, **kwargs):
# we cannot modidy cnt with out nonlocal
nonlocal cnt
cnt += 1
print(f'{fn.__name__} has been called {cnt} times')
# we are calling fn indirectly via the closue inner
return fn(*args, **kwargs)
return inner
在这个例子中,cnt是我们的自由变量,inner + cnt创建CLOSURE。当python看到这个时,它将创建一个CELL Obj, cnt将总是直接引用这个CELL Obj,而CELL将引用内存中存储cnt值的另一个Obj。最初问= 0。
cnt ======>>>> CELL =============> 0
当你通过传递参数计数器(myFunc)()调用内部函数时,这将使cnt增加1。因此,我们的引用模式将发生如下变化:
cnt ======>>>> CELL =============> 1 #first counter(myFunc)()
cnt ======>>>> CELL =============> 2 #second counter(myFunc)()
cnt ======>>>> CELL =============> 3 #third counter(myFunc)()
这只是终结的一个例子。您可以通过传递另一个函数来创建闭包的多个实例
counter(differentFunc)()
这将创建一个与上面不同的CELL obj。我们刚刚创建了另一个闭包实例。
cnt ======>> difCELL ========> 1 #first counter(differentFunc)()
cnt ======>> difCELL ========> 2 #secon counter(differentFunc)()
cnt ======>> difCELL ========> 3 #third counter(differentFunc)()
其他回答
人们对什么是终结感到困惑。闭包不是内部函数。封闭的意义是封闭的行为。内部函数封闭在一个非局部变量上,这个变量叫做自由变量。
def counter_in(initial_value=0):
# initial_value is the free variable
def inc(increment=1):
nonlocal initial_value
initial_value += increment
print(initial_value)
return inc
当你调用counter_in()时,它将返回inc函数,该函数有一个自由变量initial_value。所以我们创建了一个CLOSURE。人们称inc为闭包函数,我认为这让人困惑,人们认为"内部函数是闭包"在现实中inc并不是一个闭包,因为它是闭包的一部分,为了方便起见,他们称它为闭包函数。
myClosingOverFunc=counter_in(2)
返回inc函数,该函数在自由变量initial_value上关闭。当你调用myClosingOverFunc
myClosingOverFunc()
它会输出2。
当python看到一个闭包系统存在时,它会创建一个名为CELL的新obj。这将只存储自由变量的名称,在本例中为initial_value。这个Cell obj将指向另一个存储initial_value值的对象。
在我们的例子中,外层函数和内部函数中的initial_value将指向这个单元格对象,而这个单元格对象将指向initial_value的值。
variable initial_value =====>> CELL ==========>> value of initial_value
所以当你调用counter_in时,它的作用域消失了,但这无关紧要。因为变量initial_value直接引用CELL对象。它间接引用initial_value的值。这就是为什么即使外部函数的作用域消失了,内部函数仍然可以访问自由变量
假设我想写一个函数,它接受一个函数作为参数,并返回这个函数被调用的次数。
def counter(fn):
# since cnt is a free var, python will create a cell and this cell will point to the value of cnt
# every time cnt changes, cell will be pointing to the new value
cnt = 0
def inner(*args, **kwargs):
# we cannot modidy cnt with out nonlocal
nonlocal cnt
cnt += 1
print(f'{fn.__name__} has been called {cnt} times')
# we are calling fn indirectly via the closue inner
return fn(*args, **kwargs)
return inner
在这个例子中,cnt是我们的自由变量,inner + cnt创建CLOSURE。当python看到这个时,它将创建一个CELL Obj, cnt将总是直接引用这个CELL Obj,而CELL将引用内存中存储cnt值的另一个Obj。最初问= 0。
cnt ======>>>> CELL =============> 0
当你通过传递参数计数器(myFunc)()调用内部函数时,这将使cnt增加1。因此,我们的引用模式将发生如下变化:
cnt ======>>>> CELL =============> 1 #first counter(myFunc)()
cnt ======>>>> CELL =============> 2 #second counter(myFunc)()
cnt ======>>>> CELL =============> 3 #third counter(myFunc)()
这只是终结的一个例子。您可以通过传递另一个函数来创建闭包的多个实例
counter(differentFunc)()
这将创建一个与上面不同的CELL obj。我们刚刚创建了另一个闭包实例。
cnt ======>> difCELL ========> 1 #first counter(differentFunc)()
cnt ======>> difCELL ========> 2 #secon counter(differentFunc)()
cnt ======>> difCELL ========> 3 #third counter(differentFunc)()
闭包发生在函数从已完成执行的封闭作用域访问局部变量时。
def make_printer(msg):
def printer():
print(msg)
return printer
printer = make_printer('Foo!')
printer()
当调用make_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只是函数打印机的一个普通局部变量。
我想提供另一个简单的比较python和JS的例子,如果这有助于使事情更清楚。
JS:
function make () {
var cl = 1;
function gett () {
console.log(cl);
}
function sett (val) {
cl = val;
}
return [gett, sett]
}
和执行:
a = make(); g = a[0]; s = a[1];
s(2); g(); // 2
s(3); g(); // 3
Python:
def make ():
cl = 1
def gett ():
print(cl);
def sett (val):
cl = val
return gett, sett
和执行:
g, s = make()
g() #1
s(2); g() #1
s(3); g() #1
原因:正如上面许多人所说,在python中,如果内部作用域对同名变量赋值,则会在内部作用域中创建一个新的引用。JS则不是这样,除非你显式地用var关键字声明一个。
这个问题aaronasterling已经回答过了
然而,有些人可能对变量在底层是如何存储的感兴趣。
在开始之前:
闭包是从其封闭环境中继承变量的函数。当你将一个回调函数作为参数传递给另一个将要执行I/O操作的函数时,这个回调函数将在以后被调用,并且这个函数将——几乎神奇地——记住声明它的上下文,以及该上下文中所有可用的变量。
如果一个函数不使用自由变量,它就不能形成闭包。 如果存在另一个使用自由变量的内部关卡——所有之前的关卡都会保存词汇环境(例如最后的例子) python中的函数属性func_closure < 3。3. python >中的__closure__X保存自由变量。 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函数都有一个闭包属性,所以让我们检查一下与闭包函数相关的封闭变量。
下面是函数打印机的func_closure属性
>>> 'func_closure' in dir(printer)
True
>>> printer.func_closure
(<cell at 0x108154c90: str object at 0x108151de0>,)
>>>
闭包属性返回一个单元格对象元组,其中包含在封闭范围内定义的变量的详细信息。
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时,它会引用所有保存的自由变量,所以我们得到I是自由变量
>>> inn('variable')
'I am free variable'
>>>
def nested1(num1):
print "nested1 has",num1
def nested2(num2):
print "nested2 has",num2,"and it can reach to",num1
return num1+num2 #num1 referenced for reading here
return nested2
给:
In [17]: my_func=nested1(8)
nested1 has 8
In [21]: my_func(5)
nested2 has 5 and it can reach to 8
Out[21]: 13
这是一个关于闭包是什么以及如何使用它的例子。