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

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

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


当前回答

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

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对闭包的支持很弱。要理解我的意思,请参考以下使用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范围来存储变量

def initCounter ():
    scope = Object()
    scope.x = 0
    def counter():
        scope.x += 1
        print scope.x

    return counter

由于作用域实际上只是一个引用,对其字段所采取的操作并不真正修改作用域本身,因此不会出现错误。

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
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和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 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 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关键字将内部函数绑定到显式提到的外部变量,实际上将其封闭起来。因此更明确的是一个“闭包”。