Python闭包函数详解:原理、验证与实战应用

竹里
竹里
发布时间:2026-10-13| 2人浏览过
原创

本文深入解析Python中的闭包函数,阐述其定义、内部变量引用机制及LEGB查找规则。通过代码示例演示如何验证闭包(__closure__属性),并探讨闭包在保持状态、实现延迟计算及封装作用域方面的核心优势与注意事项。

闭包函数初探

通常情况下,我们定义函数的格式如下所示。

def foo():
 pass

然而,在函数式编程的语境下,函数内部还可以嵌套定义其他函数,具体结构如以下示例所示。

def foo():
 print("hello world in foo")
 
 def bar():
 print("hello world in bar")

当我们调用外层的foo函数时,其执行结果会呈现何种状态?

hello world in foo

如上所示,仅执行了foo函数的第一层逻辑,而内部的bar函数并未被执行。这是为什么呢?

从原理上讲,无论函数定义在代码的哪个位置,它仅仅是一个定义过程。只有当该函数被显式调用时,其内部的代码语句才会真正执行。

在上面的示例中,虽然bar函数是在foo函数内部定义的,但由于未被调用,因此它不会执行。这是否意味着定义在函数内部的函数毫无用处?其实并非如此。

请看下面的例子,我们将bar函数作为一个值返回给foo函数,并观察其执行过程。

def foo():
 print("hello world in foo")
 
 def bar():
 print("hello world in bar")
 return bar
f1=foo()
print(f1)

此时,由于bar函数作为返回值被返回给了foo,因此foo函数的执行结果包含了一个返回值。

接着,我们定义一个变量f1来接收foo函数的执行返回结果,并打印f1的值。

返回的结果如下所示。

hello world in foo
<function foo.<locals>.bar at 0x0000000002941A60>

可以看到,首先打印了foo函数中定义的一个print语句,接着打印的是foo函数中包含的bar函数的内存地址。

既然返回的是一个函数的内存地址,我们当然可以加上括号来执行这个函数。

def foo():
 print("hello world in foo")
 def bar():
 print("hello world in bar")
 return bar
f1=foo()
f1()

此时,这段代码的执行结果为:

hello world in foo
hello world in bar

两个print语句都被成功打印出来了。

在上面的例子中,首先定义了一个函数foo,接着在foo函数内部又嵌套定义了一个函数bar,然后返回函数bar的函数名,这就是闭包函数的基本定义方式。

其实,闭包的定义就是一个函数内部又嵌套了一个函数。

来看下面的这段代码。

 def foo():
 print("hello world in foo")
 name="python"
 def bar():
  print(name)
  print("hello world in bar")
 return bar
 
 f1=foo()
 f1()

在上面的例子中,我们在外层函数中定义了一个变量name,然后在内层函数中打印这个变量name。

此时执行上面的代码,在打印name这个变量的时候,会先在bar函数内部查找name这个变量,但是bar函数里面是没有name这个变量的。

此时根据Python查找变量的LEGB法则(Local->Enclose->Global->Built-in),会到bar函数的外面一层去继续查找name这个变量,此时可以找到name这个变量。

所以这里打印的是foo函数中定义的name的值。

执行上面的代码,打印结果如下。

hello world in foo
python
hello world in bar

这里要记住很重要的一点就是:

内层函数引用了外层函数的局部变量。

来分析下上面的例子中程序的执行过程:

首先运行foo函数,foo函数的执行结果是返回bar的函数名,此时又把foo函数的执行结果定义给了变量f1,所以此时f1就等于bar这个函数的内存地址,然后f1加括号运行就表示运行了bar函数。在执行bar函数的过程中,bar函数访问到了外层foo函数中定义的变量,这就是一个典型的闭包函数。那使用闭包函数有什么好处呢?在上面的例子里,f1的值是bar函数的内存地址,f1加括号运行就是在运行bar函数。

又由于f1是一个全局变量,这意味着可以在整个程序的任意位置都可以运行f1函数,此时再定义一个函数,在这个函数内部调用f1函数。

 def foo():
 print("hello world in foo")
 name = "python"
 
 def bar():
  print(name)
  print("hello world in bar")
 return bar
 
 f1 = foo()
 
 def func():
 name = "aaaaa"
 f1()
 func()

来分析一下程序的执行过程:

1.运行func函数,程序会先在内存中申请一块空间以保存name变量的值,然后运行f1函数,f1是在全局中定义的变量,所以一定可以找到f1函数的内存地址。

2.f1加括号运行,就是在执行一个闭包函数,这个闭包函数内部引用了name这个变量。

3.name这个变量在bar函数的外部已经定义了,所以在func函数内部调用f1函数,也就是bar函数时,其引用的变量依然是foo函数内部定义的name变量,而不是func函数内部定义的name变量。

4.因为f1函数的内部已经包含了name这个变量的值,所以就算在func函数内部也定义了name这个变量,程序执行的结果打印的依然是foo函数内部定义的name的值。

程序执行结果。

hello world in foo
python
hello world in bar

怎样验证一个函数是闭包函数?

首先,闭包函数都有一个特有的属性:closure。

在上面的例子里,打印f1的__closure__属性。

 def foo():
 name = "python"
 def bar():
  print(name)
  print("hello world in bar")
 return bar
 f1 = foo()
 print(f1.__closure__)

打印结果如下:

(<cell at 0x0000000001DF5708: str object at 0x0000000001E79688>,)

可以看到__closure__属性的打印结果是一个元组形式的,其值就是f1函数的外层函数作用域。

此时可以调用__closure__返回的元组的元素的cell_contents方法打印出name变量的值。

 def foo():
 name = "python"
 
 def bar():
  print(name)
  print("hello world in bar")
 return bar
 
 f1 = foo()
 print(f1.__closure__[0].cell_contents)

打印结果如下:

python

可以看到程序已经打印出name变量的值了。

既然__closure__的返回结果是一个元组,那么这个元组中一定是可以包含多个值的,看下面的例子。

在foo函数内部定义多个变量,然后在bar函数内部打印几个变量的值。

然后运行这个闭包函数,打印闭包函数的__closure__方法。

 def foo():
 print("hello world in foo")
 name1 = "python1"
 name2 = "python2"
 name3 = "python3"
 name4 = "python4" 
 def bar():
  print(name1)
  print(name2)
  print(name3)
  print(name4)
  print("hello world in bar")
 return bar 
 f1 = foo()
 print(f1.__closure__)

程序执行结果。

(<cell at 0x0000000002145708: str object at 0x00000000021C9260>, 
<cell at 0x0000000002145A08: str object at 0x00000000021C93B0>, 
<cell at 0x0000000002145768: str object at 0x000000000295BE30>, 
<cell at 0x0000000002145C18: str object at 0x0000000002963880>)

由于在foo函数内部定义了4个变量,而且在bar函数内部引用了这4个变量,所以打印这个闭包函数的__closure__方法,返回的元组中就有4个元素。

现在可以分别打印返回的元组中的这4个字符串对象的值了。

 def foo():
 name1 = "python1"
 name2 = "python2"
 name3 = "python3"
 name4 = "python4"
 
 def bar():
  print(name1)
  print(name2)
  print(name3)
  print(name4)
  print("hello world in bar")
 return bar 
 f1 = foo()
 print(f1.__closure__[0].cell_contents)
 print(f1.__closure__[1].cell_contents)
 print(f1.__closure__[2].cell_contents)
 print(f1.__closure__[3].cell_contents)

程序执行结果。

python1
python2
python3
python4

那么现在还剩下最后一个问题了,那就是闭包函数的内层函数一定要返回吗?

来看下面一个例子。

 def foo():
 name = "python1" 
 def bar():
  print(name)
 print(bar.__closure__) 
 foo()

定义了一个嵌套函数,然后这个嵌套函数的内层函数没有被返回,而是直接打印内层函数的__closure__方法,然后直接调用外层函数。

程序执行结果。

(<cell at 0x0000000002155708: str object at 0x00000000021D9688>,)

依然打印出了内层函数的引用的变量对象。

这说明闭包函数的内层函数不一定要返回。

闭包函数的内层函数可以调用全局变量吗?

把外层函数内部定义的变量改为全局变量,然后在内层函数中引用这个变量。

 name = "python1"
 def foo():
 def bar():
  print(name) 
 print(bar.__closure__)
 f=foo()
 print(f)

程序执行结果。

None None

可以看到,程序的执行结果是两个None,嵌套函数的内层函数的__closure__函数的值为None。

这说明foo函数的内层嵌套函数bar调用的全局变量没有成功,所以上面的例子不是一个闭包函数。

关于闭包函数的一些总结:

闭包的定义为:

在函数内部定义的函数,称为内部函数 内部函数调用了外部函数的局部变量 即使内部函数返回了,还是可以使用局部变量 通常闭包函数的内层函数都要被返回给外部函数 闭包函数的外部函数可以在任何地方被调用,而不再受函数定义时层级的限制

闭包函数的作用

1.闭包函数自带函数作用域

正常意义上的函数,在函数执行过程中查找变量的顺序是一层一层向外找,符合LEGB(Local->Enclose->Global->Built in)法则的。

但是对闭包函数来说,查找变量只会找内部函数外面的那一层,因为闭包函数本身就自带一层作用域,这样才符合"闭包"两个字的意思。

2.延迟计算(也叫惰性计算)

看下面的例子。

 def func():
 name="python"
 def bar():
  print(name)
 return bar 
 f=func()
 print(f.__closure)

在上面的例子里,执行foo()函数的返回结果是一个包含自带的某种状态的函数,实际上这个函数并没有执行。

以后想执行这个自带状态的函数时,把func()返回结果所赋值的那个变量加括号就可以执行了。

3.要想让一个函数始终保持一种状态,就可以使用闭包。

例子:

 name="python" 
 def func():
 print("I like %s" % name) 
 func()

上面的代码执行结果会打印一行:"I like python"。

但是我们知道,在不同的地方调用func函数,打印的结果很大可能是不一样的。

那么如果我想不管在什么地方调用func函数,打印的结果都是"I like python"时。

就可以使用闭包了。

 def func1(): 
 name="python"
 def func():
  print("I like %s" % name)
 return func
 func=func1()
 func()

如上图所示,在func函数外面再包含一层函数func1,执行func1函数,再把func1函数的返回结果赋值给func这个变量。

此时func就是一个闭包函数了,把func函数加括号就可以执行了。

而且我们一定知道,此时func函数的执行结果一定会打印"I like python"这句话,而且不管func函数在程序的哪个位置被调用,执行结果都是一样的

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。