首页 文章

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

提问于
浏览
209

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

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

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

7 回答

  • 334

    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 关键字将内部函数绑定到显式提到的外部变量,实际上将其封闭 . 因此更明确地'closure' .

  • 0
    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
    

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

  • 84

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

    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 时会发生这种情况,因此在 make_printer 返回后不需要保留对 printer 外部的 msg 值的引用 . msg 只是此上下文中函数 printer 的正常局部变量 .

  • 58

    The question has already been answered by aaronasterling

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

    Before coming to the snippet:

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

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

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

    • 函数属性 func_closure in python < 3.X or closure in python > 3.X save the free variables.

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

    example: of closure attributes but no content inside as there is no free variable.

    >>> def foo():
    ...     def fii():
    ...         pass
    ...     return fii
    ...
    >>> f = foo()
    >>> f.func_closure
    >>> 'func_closure' in dir(f)
    True
    >>>
    

    NB: FREE VARIABLE IS MUST TO CREATE A CLOSURE.

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

    >>> def make_printer(msg):
    ...     def printer():
    ...         print msg
    ...     return printer
    ...
    >>> printer = make_printer('Foo!')
    >>> printer()  #Output: Foo!
    

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

    这是函数 func_closure 的属性 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
     >>>
    

    在上面的代码片段中,我不打印打印机功能内的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')
    

    因此,我们看到 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'
    >>>
    
  • 8

    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如何管理函数变量范围 . 虽然内部函数可以__69935_外部函数的变量,但它们不能 write .

    这真是一种耻辱 . 但是只使用只读闭包,你至少可以实现Python提供语法糖的function decorator pattern .

    Update

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

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

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

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

    我想在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
    

    蟒蛇:

    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
    

    Reason: 如上所述,在python中,如果内部作用域中的赋值具有相同名称的变量,则会在内部作用域中创建一个新引用 . JS不是这样,除非您使用 var 关键字明确声明一个 .

相关问题