[Python进阶]Python闭包的深入浅出

简介: [Python进阶]Python闭包的深入浅出

前提准备


首先我们要明白在python中一切皆对象,数字、字符串、元组、列表、字典、函数、方法、类、模块等等都是对象。


因为函数也是一个对象,所以函数能够像其他变量一样被当作参数传给其他的函数,同样函数能也能够作为另一个函数的结果返回

map、sorted,filter 这几个函数都可以接受一个函数作为参数,具体用法就不在此赘述了


自由变量:指未在本地作用域绑定的变量,是相对来说的

建议看一下这个文章自由变量


函数闭包


闭包产生的原因:出现了函数嵌套,且内层函数使用了不是自己作用域的变量


一般情况下,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。


闭包简而言之就是将数据和内部函数封装到一个包(区域)中

函数的闭包就是一种函数嵌套的一种特殊情况


闭包的使用方式


在讲解闭包之前我们先看一些例子,希望大家带着疑问去学习,去理解后面的讲解


方式一:将某些变量定义在函数内部,不与全局的变量发生冲突


def outer():
    name = "fish"  
    def inner():  # 子函数可以有很多个
        print(name)
    return inner
f1 = outer()
f1() # fish


我们可以看到,虽然name = "fish" 是局部的变量,但是在外部调用f1()的时候仍然可以正常的使用


  • 方式二:将外部数据封装到函数内部(重点)
def outer(name):
    def inner():
        print(name)
    return inner   # 将内部的函数返回出去
name = 'fish'
f1 = outer(name)
name = 'tom'
f2 = outer(name)
f1()  # 输出 fish
f2()  # 输出 tom


错误理解:在调用函数f1,f2时因为内部没有定义name变量,所以函数执行时会去全局变量中找,此时全局变量name=tom结果应该都会输出 tom

但是实际输出f1()输出fish,f2()输出tom


我们能够发现在调用f1(),f2()使用的变量并没有随着全局变量的变化而改变,仍然是创建f1(),f2()函数对象时的内容


再看一个例子

def outer(x):
    def inner(y):
        print(x*y)
    return inner
x = 2  
f1 = outer(x)
x = 3
f2 = outer(x)
f1(2)      # 4 等价于 2*2
f2(3)      # 8 等价于 3*3


错误理解:在调用函数f1,f2时因为内部没有定义x变量,所以函数执行时会去全局变量中找,此时全局变量x=3所以 f1(2) 应该是 3*2, f2(3) 应该是 3*3

实际情况:f1(2) 是 2*2, f2(3) 是 3*3


总结

通过上面三个例子我们能够发现在调用f1(),f2()使用的变量并没有随着全局变量的变化而改变,仍然是创建f1(),f2()函数对象时的内容

原因就是当执行外部函数outer之后,传入的变量就和函数对象绑定到了一起(闭包),当再次调用返回的函数对象f1或f2的时候,就会发现函数对象使用的是和自己绑定的数据。


闭包的理论讲解


下面我们一步步的解释这个原因,由表及里的解释

每执行一遍fn = outer(x) 就会生成一个函数对象,每一个函数对象都有自己的内存空间,也会被存储到这个内存空间,这个过程就叫闭包,这个变量叫做自由变量后面再使用变量的时候就会从这个内存空间里面取值

c3ce487c70f74e9593dfd66917182f25.png


注:这个图不是实际的内存结构图,只是为了便于讲解


现在是不是有个疑问,怎么就闭包了?自由变量到底存到了哪里?


下面我们就根据源码来探究这个问题


在回忆一下文章开头说的那句话python中一切皆对象,函数也是对象,是对象就有属性,我们现在就有理由怀疑自由变量被存到了函数对象的属性中。


事实上自由变量就是存到了函数对象的属性中,我先说明出来,后面会有代码的验证


当执行执行outer函数结束时,如果内部函数inner中如果将来会使用自由变量,那么内部函数inner的__closure__ 属性就会保存需要的自由变量,该属性记录着自由变量的地址。当闭包被调用时,系统就会根据该地址找到对应的自由变量,完成整体的函数调用,又因为f1和f2不是同一个函数对象,所以函数对象的__closure__ 属性保存的数据也不同。


闭包的代码验证


下面让我们用代码验证


  • 内部函数没有使用自由变量,此时它的__closure__属性为None
def outer():
    def inner():
        print('hello')
    return inner
f = outer()
print(f.__closure__) # None
  • 内部函数使用自由变量,此时它的__closure__属性是一个元组里面存储自由变量
def outer():
    name = 'fish'
    age = 112
    def inner():
        print(name,age)
    return inner
f = outer()
print(f.__closure__) 
# (<cell at 0x000001ED4A6BAFD0: int object at 0x000001ED30E85750>, <cell at 0x000001ED4A6BAFA0: str object at 0x000001ED30FF74F0>)


内部函数没有使用自由变量:函数对象的__closure__属性没有值,

内部函数使用自由变量: 函数对象的__closure__属性是一个元组,里面存着自由变量的地址,__closure__属性可以存储多个自由变量的内容


最终我们回到一开始的三个例子中,验证__closure__属性是不是闭包中数据存放的位置


# 依旧是实例中的函数
def outer(name):
    def inner():
        print(name)
    return inner   # 将内部的函数返回出去
name = 'fish'
f1 = outer(name)
name = 'tom'
f2 = outer(name)
# 解释一下代码的含义
# 取出__closure__[0]中的第一个元素,返回的是一个cell单元格对象
# .cell_contents 获取cell单元格对象具体的值
print(f1.__closure__) #(<cell at 0x000001615108AA90: str object at 0x00000161379E4B70>,)
print(f1.__closure__[0].cell_contents)  # fish
print(f2.__closure__[0].cell_contents)  # tom
f1()  # 输出 fish
f2()  # 输出 tom


通过我们的实际操作以及输出,我们发现__closure__属性保存的内容就是创建函数对象时的自由变量


经过理论加代码验证相信你已经能够理解Python的闭包的真正情况,我们可以很明显的认识到原来闭包也没有这么神秘,


最后我们总结一下函数的闭包


闭包就是嵌套函数的一种特殊情况,内层函数需要使用自由变量,这个变量不会随着外层函数的结束而被销毁,它会被保存到函数对象的__closure__属性中。


闭包的实战


  • 装饰器

Python中大名鼎鼎的装饰器的实现就有闭包的思想,关于这个装饰器,这里就不具体阐述,过两天我会更新一个关于装饰器的博客

  • 多线程爬虫
    暂时看不懂也没有关系
from concurrent.futures.thread import ThreadPoolExecutor
import requests
def download(url):
    header = {
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
    }
    res = requests.get(url=url,headers=header)
    return res.text
def outer(file_name):
    def write_file(response):
        # print(type(response.result()))
        content = response.result()
        print(file_name)
        with open(file_name,'w',encoding='utf8') as fp:
            fp.write(content)
    return write_file
Pool = ThreadPoolExecutor(5)
url_arr = [
    ("https://www.163.com/news/article/HJQC8KGG000189FH.html",'1.txt'),
    ("https://www.163.com/news/article/HJQCBM89000189FH.html",'2.txt'),
    ("https://www.163.com/news/article/HJPTU9E8000189FH.html",'3.txt'),
    ("https://www.163.com/news/article/HJQ64VA1000189FH.html",'4.txt'),
    ("https://www.163.com/news/article/HJQ0V8VJ000189FH.html",'5.txt')
]
for item in url_arr:
    future = Pool.submit(download,item[0])  # 通过submit提交执行的函数到线程池中
    future.add_done_callback(outer(item[1]))
Pool.shutdown()


总结


通过这篇文章希望大家能够明白什么是Python的闭包,通过代码验证我们可以闭包并不神秘,只是函数对象的一个属性保存了自由变量。

Python的闭包独自使用的情况很少,通常都是和其他的应用一块使用,但是闭包这个思想很重要,面试经常问到。

相关文章
|
1月前
|
监控 测试技术 Python
颠覆传统!Python闭包与装饰器的高级实战技巧,让你的项目效率翻倍
【7月更文挑战第7天】Python的闭包与装饰器是强大的工具。闭包是能记住外部作用域变量的内部函数,常用于动态函数创建和工厂模式。例如,`make_power`返回含外部变量`n`的`power`闭包。装饰器则允许在不修改函数代码的情况下添加新功能,如日志或性能监控。`my_decorator`函数接收一个函数并返回包装后的函数,添加了前后处理逻辑。掌握这两者,可提升编程效率和灵活性。
30 3
|
21天前
|
数据采集 网络协议 数据挖掘
网络爬虫进阶之路:深入理解HTTP协议,用Python urllib解锁新技能
【7月更文挑战第30天】网络爬虫是数据分析和信息聚合的关键工具。深入理解HTTP协议及掌握Python的urllib库对于高效爬虫开发至关重要。HTTP协议采用请求/响应模型,具有无状态性、支持多种请求方法和内容协商等特点。
21 3
|
26天前
|
网络协议 开发者 Python
网络编程小白秒变大咖!Python Socket基础与进阶教程,轻松上手无压力!
【7月更文挑战第25天】在网络技术快速发展的背景下, Python因其简洁的语法和强大的库支持成为学习网络编程的理想选择。
43 5
|
28天前
|
机器学习/深度学习 数据采集 算法
Python编程语言进阶学习:深入探索与高级应用
【7月更文挑战第23天】Python的进阶学习是一个不断探索和实践的过程。通过深入学习高级数据结构、面向对象编程、并发编程、性能优化以及在实际项目中的应用,你将能够更加熟练地运用Python解决复杂问题,并在编程道路上走得更远。记住,理论知识只是基础,真正的成长来自于不断的实践和反思。
|
25天前
|
网络协议 Python
网络世界的建筑师:Python Socket编程基础与进阶,构建你的网络帝国!
【7月更文挑战第26天】在网络的数字宇宙中,Python Socket编程是开启网络世界大门的钥匙。本指南将引领你从基础到实战,成为网络世界的建筑师。
40 2
|
26天前
|
开发者 Python
Python Socket编程:不只是基础,更有进阶秘籍,让你的网络应用飞起来!
【7月更文挑战第25天】在网络应用蓬勃发展的数字时代,Python凭借其简洁的语法和强大的库支持成为开发高效应用的首选。本文通过实时聊天室案例,介绍了Python Socket编程的基础与进阶技巧,包括服务器与客户端的建立、数据交换等基础篇内容,以及使用多线程和异步IO提升性能的进阶篇。基础示例展示了服务器端监听连接请求、接收转发消息,客户端连接服务器并收发消息的过程。进阶部分讨论了如何利用Python的`threading`模块和`asyncio`库来处理多客户端连接,提高应用的并发处理能力和响应速度。掌握这些技能,能使开发者在网络编程领域更加游刃有余,构建出高性能的应用程序。
18 3
|
25天前
|
SQL 安全 Go
SQL注入不可怕,XSS也不难防!Python Web安全进阶教程,让你安心做开发!
【7月更文挑战第26天】在 Web 开发中, SQL 注入与 XSS 攻击常令人担忧, 但掌握正确防御策略可化解风险. 对抗 SQL 注入的核心是避免直接拼接用户输入至 SQL 语句. 使用 Python 的参数化查询 (如 sqlite3 库) 和 ORM 框架 (如 Django, SQLAlchemy) 可有效防范. 防范 XSS 攻击需严格过滤及转义用户输入. 利用 Django 模板引擎自动转义功能, 或手动转义及设置内容安全策略 (CSP) 来增强防护. 掌握这些技巧, 让你在 Python Web 开发中更加安心. 安全是个持续学习的过程, 不断提升才能有效保护应用.
35 1
|
8天前
|
数据安全/隐私保护 Python
Python闭包:函数定义的神秘力量!
Python闭包:函数定义的神秘力量!
30 0
|
1月前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
【7月更文挑战第12天】归并排序是高效稳定的排序算法,采用分治策略。Python 实现包括递归地分割数组及合并已排序部分。示例代码展示了如何将 `[12, 11, 13, 5, 6]` 分割并归并成有序数组 `[5, 6, 11, 12, 13]`。虽然 $O(n log n)$ 时间复杂度优秀,但需额外空间,适合大规模数据排序。对于小规模数据,可考虑其他算法。**
57 4
|
1月前
|
算法 Python
Python算法高手进阶指南:分治法、贪心算法、动态规划,掌握它们,算法难题迎刃而解!
【7月更文挑战第10天】探索Python算法的精华:分治法(如归并排序)、贪心策略(如找零钱问题)和动态规划(解复杂问题)。通过示例代码揭示它们如何优化问题解决,提升编程技能。掌握这些策略,攀登技术巅峰。
40 2