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

Sri*_*aju 234 python closures nested-function

我已经在Python中看到并使用了嵌套函数,它们与闭包的定义相匹配.那他们为什么叫nested functions而不是closures

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

更新:我正在阅读关于闭包的内容,这让我想到了关于Python的这个概念.我在下面的评论中搜索并找到了某人提到的文章,但我无法完全理解该文章中的解释,所以这就是我提出这个问题的原因.

aar*_*ing 374

当函数可以从已完成执行的封闭范围访问局部变量时,就会发生闭包.

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

printer = make_printer('Foo!')
printer()
Run Code Online (Sandbox Code Playgroud)

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!
Run Code Online (Sandbox Code Playgroud)

在这里,我们将值绑定到参数的默认值.在printer创建函数时会发生这种情况,因此 在返回后不需要保留对msgexternal 的值的引用.在这种情况下,它只是函数的正常局部变量.printermake_printermsgprinter

  • @mikerobi:一个代码块是否是一个闭包取决于它是否关闭它的环境,而不是你所说的.它可以是例程,函数,过程,方法,块,子例程等等.在Ruby中,方法不能是闭包,只有块可以.在Java中,方法不能是闭包,但类可以.这并没有使它们成为一个封闭.(虽然事实上他们只关闭*某些*变量,并且他们无法修改它们,使它们接下来无用.)你可以说,一个方法只是一个关闭`self`的过程.(在JavaScript/Python中几乎是真的.) (26认同)
  • @mikerobi,我不确定我们是否需要考虑函数式编程,因为python实际上并不是一种函数式语言,尽管它当然可以这样使用.但是,不,内部函数在这个意义上不是函数,因为它们的全部意义在于创建副作用.尽管如此,创建一个能够说明要点的函数也很容易, (6认同)
  • @EvgeniSergeev"关闭"即从一个封闭范围引用"局部变量[比如说,'i`]".即使/当该范围"已完成其执行"时,也就是说,可以检查(或改变)`i'的值,即程序的执行已经传递到代码的其他部分.定义`i`的块不再存在,但是引用`i`的函数仍然可以这样做.这通常被描述为"关闭变量`i`".为了不处理特定变量,可以将其实现为关闭定义该变量的整个环境框架. (4认同)
  • 您的回答比我的要好得多,您的观点很好,但是如果我们要遵循最严格的函数式编程定义,那么您的示例甚至是函数?已经有一段时间了,我不记得严格的函数编程是否允许不返回值的函数。如果您认为返回值是None,那是没有意义的,但这是另一个主题。 (2认同)
  • @JörgWMittag请定义"结束". (2认同)
  • 从技术上讲,闭包是函数的*外部*。闭包是一个单独的命名空间,它附加到一个嵌套函数来存储这些变量。 (2认同)

Jam*_*pam 97

aaronasterling 已经回答了这个问题

但是,有人可能会对变量如何存储在引擎盖下感兴趣.

在进入片段之前:

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

  • 如果函数不使用自由变量,则它不会形成闭包.

  • 如果有另一个内部级别使用自由变量 - 所有以前的级别都保存了词法环境(最后的例子)

  • 功能属性func_closure蟒<3.X或__closure__在python> 3.X保存自由变量.

  • python中的每个函数都有这个闭包属性,但如果没有自由变量,它不会保存任何内容.

示例:闭包属性但内部没有内容,因为没有自由变量.

>>> def foo():
...     def fii():
...         pass
...     return fii
...
>>> f = foo()
>>> f.func_closure
>>> 'func_closure' in dir(f)
True
>>>
Run Code Online (Sandbox Code Playgroud)

注意:免费变量必须创建一个闭合.

我将解释使用与上面相同的代码段:

>>> def make_printer(msg):
...     def printer():
...         print msg
...     return printer
...
>>> printer = make_printer('Foo!')
>>> printer()  #Output: Foo!
Run Code Online (Sandbox Code Playgroud)

并且所有Python函数都有一个闭包属性,所以让我们检查与闭包函数关联的封闭变量.

这是func_closure函数的属性printer

>>> 'func_closure' in dir(printer)
True
>>> printer.func_closure
(<cell at 0x108154c90: str object at 0x108151de0>,)
>>>
Run Code Online (Sandbox Code Playgroud)

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']
>>>
Run Code Online (Sandbox Code Playgroud)

在上面的输出中你可以看到cell_contents,让我们看看它存储的内容:

>>> printer.func_closure[0].cell_contents
'Foo!'    
>>> type(printer.func_closure[0].cell_contents)
<type 'str'>
>>>
Run Code Online (Sandbox Code Playgroud)

所以,当我们调用函数时printer(),它会访问存储在函数中的值cell_contents.这就是我们将输出视为'Foo!'的方式.

我将再次解释使用上面的代码片段进行一些更改:

 >>> def make_printer(msg):
 ...     def printer():
 ...         pass
 ...     return printer
 ...
 >>> printer = make_printer('Foo!')
 >>> printer.func_closure
 >>>
Run Code Online (Sandbox Code Playgroud)

在上面的代码片段中,我不打印打印机功能内的msg,因此它不会创建任何自由变量.由于没有自由变量,闭包内部将没有内容.这正是我们上面看到的.

现在,我将解释另一个不同的片段,以清除一切Free VariableClosure:

>>> 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')
Run Code Online (Sandbox Code Playgroud)

因此,我们看到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
>>>
Run Code Online (Sandbox Code Playgroud)

在我们调用时inn,它将引用所有保存自由变量,以便我们得到I am free variable

>>> inn('variable')
'I am free variable'
>>>
Run Code Online (Sandbox Code Playgroud)

  • 在Python 3中,`func_closure`现在称为`__closure__`,类似于其他各种`func_*`属性. (9认同)
  • 此外,`__closure_`在Python 2.6+中可用,以便与Python 3兼容. (3认同)

Cri*_*cia 69

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
Run Code Online (Sandbox Code Playgroud)

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();
Run Code Online (Sandbox Code Playgroud)

您将收到一条错误消息,指出x未定义.但是,如果其他人已经证明你可以打印它,怎么可能呢?这是因为Python如何管理函数变量范围.虽然内部函数可以读取外部函数的变量,但它不能写入它们.

这真是一种耻辱.但是只使用只读闭包,你至少可以实现Python提供语法糖的函数装饰器模式.

更新

正如已经指出的那样,有一些方法可以处理python的范围限制,我将揭露一些.

1.使用global关键字(一般不推荐).

2.定义一个简单的可修改类nonlocal

class Object(object):
    pass
Run Code Online (Sandbox Code Playgroud)

并创建一个 Object内部Object scope来存储变量

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

    return counter
Run Code Online (Sandbox Code Playgroud)

由于initCounter它实际上只是一个引用,因此对其字段采取的操作不会真正修改scope自身,因此不会出现错误.

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

4.如@raxacoricofallapatorius建议,你可以做x的属性x

def initCounter ():

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

    counter.x = 0
    return counter
Run Code Online (Sandbox Code Playgroud)

  • 有办法解决这个问题.在Python2中,你可以在外部作用域中创建`x = [0]`,并在内部作用域中使用`x [0] + = 1`.在Python3中,您可以保持代码不变并使用[nonlocal keyword](http://stackoverflow.com/a/1261952/190597). (27认同)
  • Python 3具有`nonlocal`关键字,它类似于`global`,但是用于外部函数的变量.这将允许内部函数从其外部函数重新绑定名称.我认为"绑定到名称"比"修改变量"更准确. (9认同)
  • 还有另一种选择,严格地优于#2,imv,[使`x`为`counter`的属性](http://stackoverflow.com/a/279597/656912). (4认同)

Lee*_*son 15

Python 2没有闭包 - 它有类似闭包的解决方法.

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

在Python 3中,支持更明确 - 而且简洁:

def closure():
    count = 0
    def inner():
        nonlocal count
        count += 1
        print(count)
    return inner
Run Code Online (Sandbox Code Playgroud)

用法:

start = closure()
start() # prints 1
start() # prints 2
start() # prints 3
Run Code Online (Sandbox Code Playgroud)

nonlocal关键词结合的内函数来明确提到的外变量,实际上包围它.因此更明确地说是"封闭".

  • 有趣,供参考:https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement。我不知道为什么在 python3 文档中很难找到更多关于闭包的信息(以及你可能期望它们如何表现,来自 JS)? (3认同)

fp_*_*ora 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
Run Code Online (Sandbox Code Playgroud)


Krc*_*n U 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
Run Code Online (Sandbox Code Playgroud)

得到:

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
Run Code Online (Sandbox Code Playgroud)

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