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

2024-12-18 08:39:00
admin
原创
163
摘要:问题描述:我在 Python 中看到并使用过嵌套函数,它们符合闭包的定义。那么为什么它们被称为“嵌套函数”而不是“闭包”呢?嵌套函数不是闭包,因为它们不被外部世界使用?更新:我正在阅读有关闭包的文章,这让我开始思考与 Python 相关的这个概念。我搜索并找到了下面评论中某人提到的文章,但我无法完全理解那篇文...

问题描述:

我在 Python 中看到并使用过嵌套函数,它们符合闭包的定义。那么为什么它们被称为“嵌套函数”而不是“闭包”呢?

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

更新:我正在阅读有关闭包的文章,这让我开始思考与 Python 相关的这个概念。我搜索并找到了下面评论中某人提到的文章,但我无法完全理解那篇文章中的解释,所以这就是我问这个问题的原因。


解决方案 1:

当函数能够访问已完成执行的封闭范围中的局部变量时,就会发生闭包。

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创建时,因此返回 后无需维护对msgexternal值的引用。在此上下文中,它只是函数的一个普通局部变量。printer`make_printermsgprinter`

解决方案 2:

aaronasterling已经回答了这个问题

然而,有人可能会对变量在底层的存储方式感兴趣。

在看代码片段之前:

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

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

  • 如果有另一个使用自由变量的内部级别——所有先前的级别都会保存词汇环境(末尾的示例)

  • python <3.X 或func_closurepython >3.X中的函数属性保存自由变量。__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 函数都有一个闭包属性,因此让我们检查一下与闭包函数相关的封闭变量。

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

解决方案 3:

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

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

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

    return counter

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

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

解决方案 4:

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

解决方案 5:

我遇到过一种情况,我需要一个独立但持久的名称空间。我使用了类。否则我不会使用。独立但持久的名称是闭包。

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

解决方案 6:

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

这是一个闭包是什么以及如何使用闭包的示例。

解决方案 7:

人们对闭包是什么感到困惑。闭包不是内部函数。闭包的含义是关闭的行为。因此,内部函数是对非局部变量(称为自由变量)的关闭。

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。所以我们创建了一个闭包。人们称之为inc闭包函数,我认为这会让人感到困惑,人们认为“好的,内部函数就是闭包”。实际上inc它不是一个闭包,因为它是闭包的一部分,为了让生活更简单,他们称之为闭包函数。

  myClosingOverFunc=counter_in(2)

这将返回inc关闭自由变量的函数initial_value。当您调用时myClosingOverFunc

 myClosingOverFunc() 

它将打印 2。

当 python 发现闭包系统存在时,它会创建一个名为 CELL 的新对象。这将仅存储自由变量的名称,initial_value在本例中为。此 Cell 对象将指向另一个存储值的对象initial_value

在我们的例子中,initial_value外部函数和内部函数都会指向这个单元格对象,而这个单元格对象又会指向 的值initial_value

  variable initial_value =====>> CELL ==========>> value of initial_value

因此,当您调用counter_in它时,其范围就消失了,但这并不重要。因为变量initial_value直接引用 CELL Obj。它间接引用的值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始终直接引用这个单元格 obj,而 CELL 将引用内存中存储值的另一个 obj cnt。最初 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)()


  

解决方案 8:

如果这有助于使事情变得更清楚,我想提供另一个 python 和 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

解决方案 9:

对于《计算机程序的结构和解释》(SICP)的读者来说:闭包两种不相关的含义(CS VS Math),有关后者/不太常见的含义,请参阅 Wikipedia:

20 世纪 80 年代,Sussman 和Abelson还使用了术语“闭包” ,但使用了第二个不相关的含义:运算符的属性,该属性使数据能够添加到数据结构中,同时还能够添加嵌套数据结构。该术语的这种用法源自数学用法,而不是计算机科学中的先前用法。作者认为这种术语重叠是“不幸的”。

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

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

  • 2.3 序列 | Python 中的 SICP

解决方案 10:

这里介绍了一种通过对象来识别函数是否为闭包的方法code

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

(Python 的) 内部类型code
对象表示已编译的函数体。其属性co_cellvarsco_freevars用于“查看”函数的闭包/作用域。如文档中所述

  • co_freevars:自由变量名称的元组(通过函数的闭包引用)

  • co_cellvars:单元格变量名称的元组(由包含范围引用)。

一旦读取了该函数,通过执行递归调用,将返回一个部分函数,​​其中包含它自己的__closure__(因此cell_contents)以及来自其闭包和其范围内的自由变量列表。

介绍一下一些支持功能

# 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
co_varnames当然还有属性。co_consts`co_codeco_lnotabco_stacksize`__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都不是闭包,因为它们在作用域中
没有单元格h3也没有自由变量。和h3闭包并且共享(在这种情况下)相同的单元格和自由变量ch4具有另一个x具有自己单元格的自由变量。


最后考虑:

  • 属性__closure____code__.co_freevars可用于检查自由变量的值和名称(标识符)

  • 和之间的反类比(广义上:作用于外部函数,而不是内部函数nonlocal`__code__.co_cellvarsnonlocal__code__.co_cellvars`

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用