为什么python不嵌套函数称为闭包?

我在Python中见过并使用过嵌套函数,它们与闭包的定义相匹配。那么为什么它们被称为“嵌套函数”呢?而不是&;closures"?

嵌套函数不是闭包,因为它们不被外部使用吗?

更新:我正在阅读关于闭包的内容,它让我思考了关于Python的这个概念。我搜索了下面评论中有人提到的文章,但我不能完全理解文章中的解释,所以我才问这个问题。

109986 次浏览

闭包发生在函数从已完成执行的封闭作用域访问局部变量时。

def make_printer(msg):
def printer():
print(msg)
return printer


printer = make_printer('Foo!')
printer()

make_printer被调用时,一个新的帧被放在堆栈上,其中printer函数的编译代码作为一个常量,msg的值作为一个局部值。然后它创建并返回函数。因为函数printer引用了msg变量,所以在make_printer函数返回后,它仍保持活动状态。

如果你的嵌套函数没有

  1. 对于封闭作用域的本地访问变量,
  2. 当它们在该范围外执行时这样做,

那么它们就不是闭包了。

下面是一个嵌套函数的例子,它不是闭包。

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的一个普通局部变量。

这个问题已经由 aaronasterling

然而,有些人可能对变量在底层是如何存储的感兴趣。

在开始之前:

闭包是从其封闭环境中继承变量的函数。当你将一个回调函数作为参数传递给另一个将要执行I/O操作的函数时,这个回调函数将在以后被调用,并且这个函数将——几乎神奇地——记住声明它的上下文,以及该上下文中所有可用的变量。

  • 如果一个函数不使用自由变量,它就不能形成闭包。

  • 如果有另一个内部层使用自由变量——所有以前的层保存词汇环境(例如在最后)

  • 函数属性python & lt;3.X或__closure__在python >3.X保存自由变量。中的func_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,因此它不会创建任何自由变量。由于没有自由变量,闭包内将没有内容。这正是我们在上面看到的。

现在我将解释另一个不同的片段,用Closure清除所有Free Variable:

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

这是一个关于闭包是什么以及如何使用它的例子。

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中x,使用nonlocal关键字(由@unutbu和@leewz建议)

定义简单的可修改类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本身,因此不会出现错误。

@unutbu指出,另一种方法是将每个变量定义为一个数组(x = [0]),并修改它的第一个元素(x[0] += 1)。同样没有出现错误,因为x本身没有被修改。

正如@raxacoricofallapatorius所建议的,你可以将x作为counter的一个属性

def initCounter ():


def counter():
counter.x += 1
print counter.x


counter.x = 0
return counter
我遇到了需要一个单独但持久的名称空间的情况。 我使用课堂。否则我不会。 分隔但持久的名称是闭包
>>> 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

Python 2没有闭包——它有就像闭包的变通方法。

已经给出的答案中有很多例子——将变量复制到内部函数,在内部函数上修改对象,等等。

在Python 3中,支持更加显式和简洁:

def closure():
count = 0
def inner():
nonlocal count
count += 1
print(count)
return inner

用法:

start = closure()
another = closure() # another instance, with a different stack


start() # prints 1
start() # prints 2


another() # print 1


start() # prints 3

nonlocal关键字将内部函数绑定到显式提到的外部变量,实际上将其封闭起来。因此更明确的是一个“闭包”。

我想提供另一个简单的比较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关键字声明一个。

人们对什么是终结感到困惑。闭包不是内部函数。封闭的意义是封闭的行为。内部函数封闭在一个非局部变量上,这个变量叫做自由变量。

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

当你通过传递参数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)()




  

对于《计算机程序的结构和解释》(SICP)的读者:关闭 (CS VS Math)有2无关的含义,后者/不太常见的含义见维基百科:

Sussman和阿贝尔森在20世纪80年代还使用了术语关闭,并有另一个不相关的含义:将数据添加到数据结构的操作符的属性,也可以添加嵌套的数据结构。这个术语的用法来自数学用法,而不是之前在计算机科学中的用法。作者认为这种术语上的重叠是“不幸的”。

第二个(数学)含义也在Python的SICP中使用,参见例如元组的讨论

我们能够将元组用作其他元组的元素,这为我们的编程语言提供了一种新的组合方式。我们将元组以这种方式嵌套的能力称为元组数据类型的关闭属性。通常,如果组合结果本身可以使用相同的方法组合,则组合数据值的方法满足闭包性质。

这里提供了一种通过code对象来识别函数是否是闭包的方法。

正如已经在其他答案中提到的,并不是每个嵌套函数都是闭包。给定一个复合函数(表示整个动作),它的中间状态可以是闭包或嵌套函数。 闭包是一种“参数化”的函数。由其(非空的)封闭范围,自由变量的空间。注意,复合函数可以由这两种类型组成

(Python的)内部类型code 对象表示编译后的函数体。它的属性co_cellvarsco_freevars可用于“lookaround"函数的闭包/作用域。 如医生

. conf中所述
  • co_freevars:自由变量名的元组(通过函数的闭包引用)
  • co_cellvars:单元格变量名的元组(由包含作用域引用)。

一旦函数被读取,通过递归调用返回一个局部函数,它带有自己的__closure__(因此是cell_contents)和它的clousre和作用域内的自由变量列表。

让我们介绍一些支持函数

# the "lookarounds"
def free_vars_from_closure_of(f):
print(f.__name__, 'free vars from its closure',  f.__code__.co_cellvars)


def free_vars_in_scopes_of(f):
print(f.__name__, 'free vars in its scope    ', f.__code__.co_freevars)


# read cells values
def cell_content(f):
if f.__closure__ is not None:
if len(f.__closure__) == 1: # otherwise problem with join
c = f.__closure__[0].cell_contents
else:
c = ','.join(str(c.cell_contents) for c in f.__closure__)
else:
c = None


print(f'cells of {f.__name__}: {c}')

这里有一个例子,来自另一个用更系统的方式重写的答案

def f1(x1):
def f2(x2):
a = 'free' # <- better choose different identifier to avoid confusion
def f3(x3):
return '%s %s %s %s' %  (x1, x2, a, x3)
return f3
return f2


# partial functions
p1 = f1('I')
p2 = p1('am')


# lookaround
for p in (f1, p1, p2):
free_vars_in_scopes_of(p)
free_vars_from_closure_of(p)
cell_content(p)

输出

f1 free vars in its scope     ()         # <- because it's the most outer function
f1 free vars from its closure ('x1',)
cells of f1: None
f2 free vars in its scope     ('x1',)
f2 free vars from its closure ('a', 'x2')
cells of f2: I
f3 free vars in its scope     ('a', 'x1', 'x2')
f3 free vars from its closure ()        # <- because it's the most inner function
cells of f3: free, I, am

对应的lambda:

def g1(x1):
return lambda x2, a='free': lambda x3: '%s %s %s %s' %  (x1, x2, a, x3)

从自由变量的角度来看/作用域是等价的。唯一微小的区别是code对象的某些属性的一些值: __abc0, __abc1, __abc2, __abc3, __abc4…和__name__属性


一个混合的例子,闭包和不立即:

# example: counter
def h1():             # <- not a closure
c = 0
def h2(c=c):      # <- not a closure
def h3(x):    # <- closure
def h4(): # <- closure
nonlocal c
c += 1
print(c)
return h4
return h3
return h2


# partial functions
p1 = h1()
p2 = p1()
p3 = p2('X')


p1() # do nothing
p2('X') # do nothing
p2('X') # do nothing
p3() # +=1
p3() # +=1
p3() # +=1


# lookaround
for p in (h1, p1, p2, p3):
free_vars_in_scopes_of(p)
#free_vars_from_closure_of(p)
cell_content(p)

输出

1 X
2 X
3 X
h1 free vars in its scope     ()
cells of h1: None
h2 free vars in its scope     ()
cells of h2: None
h3 free vars in its scope     ('c',)
cells of h3: 3
h4 free vars in its scope     ('c', 'x')
cells of h4: 3,X
h1h2都是不关闭,因为它们的作用域中没有细胞和自由变量。 h3h3闭包,并共享(在本例中)相同的细胞c的自由变量。h4还有一个自由变量x,它有自己的单元格

最后的考虑:

  • __closure__属性和__code__.co_freevars可以用来检查自由变量的值和名称(标识符)
  • nonlocal__code__.co_cellvars之间的反类比(在广泛的意义上):nonlocal作用于外部函数,而__code__.co_cellvars则作用于内部函数