Python教程第9章 | 通俗易懂学闭包

简介: 本文通过一个需求探讨闭包的基本概念与用法,帮助快速理解闭包。

闭包

什么是闭包?

闭包是在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这样就构成了一个闭包。

网上关于闭包的案例非常多,我们尽量以通俗易懂的实际案例学习闭包。

本文将通过一个需求探讨闭包

需求:我们需要记录自己的学习时间,以分钟为单位。当我学习了 2 分钟,就返回 2 ,然后隔了一阵子,我学习了 10 分钟,那么就返回 12 ,像这样把学习时间一直累加下去。

time = 0
def insert_time(min):
    time = time + min
    return  time
print(insert_time(2))
print(insert_time(10))

image.gif

认真想一下,会不会有什么问题呢?

其实,这个在 Python 里面是会报错的。会报如下错误:

UnboundLocalError: local variable 'time' referenced before assignment

image.gif

那是因为,在 Python 中,如果一个函数使用了和全局变量相同的名字且改变了该变量的值,那么该变量就会变成局部变量,那么就会造成在函数中我们没有进行定义就引用了,所以会报该错误。

如果确实要引用全局变量,并在函数中对它进行修改,该怎么做呢?

我们可以使用 global 关键字,具体修改如下:

time = 0
def insert_time(min):
    global  time
    time = time + min
    return  time
print(insert_time(2))
print(insert_time(10))

image.gif

输出结果如下:

2
12

image.gif

可是啊,这里使用了全局变量,我们在开发中能尽量避免使用全局变量的就尽量避免使用。因为不同模块,不同函数都可以自由的访问全局变量,可能会造成全局变量的不可预知性。比如程序员甲修改了全局变量 time 的值,然后程序员乙同时也对 time 进行了修改,如果其中有错误,这种错误是很难发现和更正的。

全局变量降低了函数或模块之间的通用性,不同的函数或模块都要依赖于全局变量。同样,全局变量降低了代码的可读性,阅读者可能并不知道调用的某个变量是全局变量。

那有没有更好的方法呢?

这时候我们使用闭包来解决一下,先直接看代码:

time = 0
def study_time(time):
    def insert_time(min):
        nonlocal  time
        time = time + min
        return time
    return insert_time
f = study_time(time)
print(f(2))
print(time)
print(f(10))
print(time)

image.gif

输出结果如下:

2
0
12
0

image.gif

这里最直接的表现就是全局变量 time 至此至终都没有修改过,这里还是用了 nonlocal 关键字,表示在函数或其他作用域中使用外层(非全局)变量。那么上面那段代码具体的运行流程是怎样的。我们可以看下下图:

image.gif 编辑

这种内部函数的局部作用域中可以访问外部函数局部作用域中变量的行为,我们称为: 闭包。更加直接的表达方式就是,当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。k

闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来。而且使用闭包,可以使代码变得更加的优雅。而且下一篇讲到的装饰器,也是基于闭包实现的。

到这里,就会有一个问题了,你说它是闭包就是闭包了?有没有什么办法来验证一下这个函数就是闭包呢?

有的,所有函数都有一个 __closure__ 属性,如果函数是闭包的话,那么它返回的是一个由 cell 组成的元组对象。cell 对象的 cell_contents 属性就是存储在闭包中的变量。

我们打印出来体验一下:

time = 0
def study_time(time):
    def insert_time(min):
        nonlocal  time
        time = time + min
        return time
    return insert_time
f = study_time(time)
print(f.__closure__)
print(f(2))
print(time)
print(f.__closure__[0].cell_contents)
print(f(10))
print(time)
print(f.__closure__[0].cell_contents)

image.gif

打印的结果为:

(<cell at 0x0000000000410C48: int object at 0x000000001D6AB420>,)
2
0
2
12
0
12

image.gif

从打印结果可见,传进来的值一直存储在闭包的 cell_contents 中,因此,这也就是闭包的最大特点,可以将父函数的变量与其内部定义的函数绑定。就算生成闭包的父函数已经释放了,闭包仍然存在。

结论

闭包的过程其实好比类(父函数)生成实例(闭包),不同的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时创建,一般程序执行完毕后作用域才释放,因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活。

下一章:Python教程第11章 | 通俗易懂学装饰器(终章)

相关文章
|
1天前
|
数据采集 存储 JSON
Python 数据抓取教程:完结篇
Python 数据抓取教程:完结篇
11 1
|
4天前
|
存储 安全 Java
在python中使用闭包和其他惯例
【7月更文挑战第3天】本文介绍闭包基本概念和例子,内部函数访问外部变量,实现数据隐藏。以及 Python的惯用法:用`in`检查字典键,用`dict.get()`安全取值。
14 1
在python中使用闭包和其他惯例
|
3天前
|
达摩院 语音技术 异构计算
语音识别-免费开源的语音转文本软件Whisper的本地搭建详细教程,python版本是3.805,ffmpeg是专门处理音视频的,ffmpeg的下载链接,现在要求安装python和ffmpeg
语音识别-免费开源的语音转文本软件Whisper的本地搭建详细教程,python版本是3.805,ffmpeg是专门处理音视频的,ffmpeg的下载链接,现在要求安装python和ffmpeg
|
3天前
|
自然语言处理 Python
从菜鸟到大神,一篇文章带你玩转Python闭包与装饰器的深度应用
【7月更文挑战第4天】Python中的闭包和装饰器是增强代码优雅性的关键特性。闭包是能访问外部作用域变量的内部函数,如示例中的`inner_function`。装饰器则是接收函数并返回新函数的函数,用于扩展功能,如`my_decorator`。装饰器可与闭包结合,如`repeat`装饰器,它使用闭包记住参数并在调用时重复执行原函数。这些概念提升了代码复用和可维护性。
|
5天前
|
机器学习/深度学习 自然语言处理 TensorFlow
使用Python实现深度学习模型:序列建模与生成模型的博客教程
【7月更文挑战第2天】 使用Python实现深度学习模型:序列建模与生成模型的博客教程
14 1
|
6天前
|
机器学习/深度学习 数据采集 算法
Scikit-Learn基础教程
Scikit-Learn基础教程
12 2
|
10天前
|
Shell Python
Python教程:return和yield的区别
Python教程:return和yield的区别
7 0
Python教程:return和yield的区别
|
3天前
|
Python
Python 中 decimal 模块的用法教程
Python 中 decimal 模块的用法教程
5 0
|
4天前
|
Python
惊呆了!Python 闭包与装饰器:解锁代码魔法的神秘钥匙🔑
【7月更文挑战第3天】Python中的闭包和装饰器是代码的神器。闭包是内嵌函数记住外部函数的变量,如`inner_function`记住`outer_function`的`x`。装饰器不修改原函数,增加额外功能,如`my_decorator`在`my_function`执行前后的打印。它们提升代码复用性,如`timeit_decorator`计时或`permission_required`控制访问权限。利用这些特性,编码变得更高效和优雅。
|
4天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
使用Python实现深度学习模型:迁移学习与领域自适应教程
【7月更文挑战第3天】 使用Python实现深度学习模型:迁移学习与领域自适应教程
7 0