我在Python中看到并使用过嵌套函数,它们与闭包的定义相匹配。那么为什么他们会调用nested functions
而不是closures
?
嵌套函数不是闭包,因为外部世界不使用它们吗?
更新:我正在阅读关于闭包的内容,这让我想到了关于Python的这个概念。我在下面的评论中搜索并找到了某人提到的文章,但我无法完全理解该文章中的解释,所以这就是我提出这个问题的原因。
答案 0 :(得分:359)
当函数可以从已完成执行的封闭范围访问局部变量时,就会发生闭包。
def make_printer(msg):
def printer():
print msg
return printer
printer = make_printer('Foo!')
printer()
当调用make_printer
时,会在堆栈中放入一个新帧,其中printer
函数的编译代码为常量,msg
的值为局部值。然后它创建并返回该函数。因为函数printer
引用了msg
变量,所以在make_printer
函数返回后它保持活动状态。
所以,如果您的嵌套函数不是
然后他们不是关闭。
这是一个嵌套函数的例子,它不是一个闭包。
def make_printer(msg):
def printer(msg=msg):
print msg
return printer
printer = make_printer("Foo!")
printer() #Output: Foo!
这里,我们将值绑定到参数的默认值。创建函数printer
时会发生这种情况,因此在msg
返回后不需要保留对printer
外部make_printer
的值的引用。 msg
只是此上下文中函数printer
的正常局部变量。
答案 1 :(得分:89)
然而,有人可能会对如何存储变量感兴趣。
在进入代码段之前:
闭包是从其封闭环境继承变量的函数。当你将函数回调作为参数传递给另一个将执行I / O的函数时,这个回调函数将在稍后调用,并且这个函数将 - 几乎神奇地 - 记住它被声明的上下文,以及所有可用的变量在这种情况下。
如果函数不使用自由变量,则它不会形成闭包。
如果有另一个内部级别使用自由变量 - 所有以前的级别保存词法环境(最后的例子)
python<中的函数属性func_closure
3.X或python中的__closure__
> 3.X保存自由变量。
python中的每个函数都有这个闭包属性,但如果没有自由变量,它就不会保存任何内容。
示例:关闭属性,但内部没有内容,因为没有自由变量。
>>> def foo():
... def fii():
... pass
... return fii
...
>>> f = foo()
>>> f.func_closure
>>> 'func_closure' in dir(f)
True
>>>
注意:FREE VARIABLE必须创建一个关闭。
我将使用与上面相同的代码段解释:
>>> def make_printer(msg):
... def printer():
... print msg
... return printer
...
>>> printer = make_printer('Foo!')
>>> printer() #Output: Foo!
所有Python函数都有一个闭包属性,所以让我们检查与闭包函数关联的封闭变量。
以下是函数func_closure
printer
>>> '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
>>>
在上面的代码片段中,我不打印打印机功能内的msg,因此它不会创建任何自由变量。由于没有自由变量,闭包内部将没有内容。这正是我们上面所看到的。
现在,我将解释另一个不同的代码段,以Free Variable
清除所有Closure
:
>>> 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 am free variable
>>> inn('variable')
'I am free variable'
>>>
答案 2 :(得分:62)
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
Closure非常优雅,因为它为这样的函数提供了具有“内部存储器”的能力。从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提供语法糖的function decorator pattern。
<强>更新强>
正如已经指出的那样,有很多方法可以解决python的范围限制,我会暴露一些。
1。使用global
关键字(一般不推荐)。
2。定义一个简单的可修改类Object
class Object(object):
pass
并在Object scope
内创建initCounter
以存储变量
def initCounter ():
scope = Object()
scope.x = 0
def counter():
scope.x += 1
print scope.x
return counter
由于scope
实际上只是一个引用,因此对其字段执行的操作并不会真正修改scope
本身,因此不会出现错误。
3. 正如@unutbu指出的那样,另一种方法是将每个变量定义为数组(x = [0]
)并修改它的第一个元素(x[0] += 1
) 。同样不会出现错误,因为x
本身未被修改。
4. 根据@raxacoricofallapatorius的建议,您可以将x
作为counter
的属性
def initCounter ():
def counter():
counter.x += 1
print counter.x
counter.x = 0
return counter
答案 3 :(得分:12)
Python 2没有闭包 - 它有类似于闭包的变通方法。
在已经给出的答案中有很多例子 - 将变量复制到内部函数,修改内部函数上的对象等。
在Python 3中,支持更明确 - 而且简洁:
def closure():
count = 0
def inner():
nonlocal count
count += 1
print(count)
return inner
用法:
start = closure()
start() # prints 1
start() # prints 2
start() # prints 3
nonlocal
关键字将内部函数绑定到显式提到的外部变量,实际上将其封闭。因此,更明确地关闭&#39;。
答案 4 :(得分:9)
我遇到的情况是我需要一个单独但持久的名称空间。 我上了课。我不这样做。 隔离但持久的名称是闭包。
>>> class f2:
... def __init__(self):
... self.a = 0
... def __call__(self, arg):
... self.a += arg
... return(self.a)
...
>>> f=f2()
>>> f(2)
2
>>> f(2)
4
>>> f(4)
8
>>> f(8)
16
# **OR**
>>> f=f2() # **re-initialize**
>>> f(f(f(f(2)))) # **nested**
16
# handy in list comprehensions to accumulate values
>>> [f(i) for f in [f2()] for i in [2,2,4,8]][-1]
16
答案 5 :(得分:5)
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
这是关闭是什么以及如何使用它的一个例子。
答案 6 :(得分:1)
人们对什么是封闭感到困惑。闭包不是内部功能。关闭的含义是关闭行为。因此内部函数正在关闭一个称为自由变量的非局部变量。
def counter_in(initial_value=0):
# initial_value is the free variable
def inc(increment=1):
nonlocal initial_value
initial_value += increment
return 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
。此单元格obj将指向另一个存储initial_value
值的对象。
在我们的示例中,外部函数和内部函数中的initial_value
指向此单元格对象,而此单元格对象将指向initial_value
的值。
variable initial_value =====>> CELL ==========>> value of initial_value
因此,当您调用counter_in
时,其作用域已消失,但这并不重要。因为变量initial_value
直接引用了CELL Obj。并且间接引用initial_value
的值。这就是为什么即使外部函数的作用域消失了,内部函数仍然可以访问自由变量的原因
假设我要编写一个函数,该函数将一个函数作为arg接收,并返回该函数被调用的次数。
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
将始终直接引用此单元格obj,而CELL将引用存储cnt
值的内存中的另一个obj。最初cnt = 0。
cnt ======>>>> CELL =============> 0
在调用内部函数并传递参数counter(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)()
答案 7 :(得分:0)
我想在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
关键字明确声明一个。