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

文章目录

*
前提准备
函数闭包
闭包的使用方式
闭包的理论讲解
闭包的代码验证
闭包的实战
总结

[Python进阶]Python闭包的深入浅出
  • 🐟 个人主页 :小鱼干儿♛
  • ⭐个人社区:【小鱼干爱编程】 期待和大家一起学习❤️❤️❤️
  • 💯 刷题网站:市面上的刷题网站有很多如何选择一个适合自己的网站呢,博主给这里推荐一款我常用的刷题网站 👉点击跳转

; 前提准备

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

因为函数也是一个对象,所以函数能够像其他变量一样被当作参数传给其他的函数,同样函数能也能够作为另一个函数的结果返回
mapsorted, filter 这几个函数都可以接受一个函数作为参数,具体用法就不在此赘述了

自由变量:指未在本地作用域绑定的变量,是相对来说的
建议看一下这个文章自由变量

函数闭包

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

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

闭包简而言之就是将数据和内部函数封装到一个包(区域)中
函数的闭包就是一种函数嵌套的一种特殊情况

闭包的使用方式

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

  • 方式一:将某些变量定义在函数内部,不与全局的变量发生冲突
def outer():
    name = "fish"
    def inner():
        print(name)
    return inner
f1 = outer()
f1()

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

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

错误理解:在调用函数 f1, f2时因为内部没有定义name变量,所以函数执行时会去全局变量中找,此时全局变量 name=tom结果应该都会输出 tom
但是实际输出 f1()输出 fishf2()输出 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)
f2(3)

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

总结
通过上面三个例子我们能够发现在调用 f1()f2()使用的变量并没有随着全局变量的变化而改变,仍然是创建 f1()f2()函数对象时的内容
原因就是当执行外部函数 outer之后,传入的变量就和函数对象绑定到了一起(闭包),当再次调用返回的函数对象 f1f2的时候,就会发现函数对象使用的是和自己绑定的数据。

闭包的理论讲解

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

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

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

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

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

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

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

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

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

; 闭包的代码验证

下面让我们用代码验证

  • 内部函数没有使用自由变量,此时它的 __closure__属性为None
def outer():
    def inner():
        print('hello')
    return inner
f = outer()
print(f.__closure__)
  • 内部函数使用自由变量,此时它的 __closure__属性是一个元组里面存储自由变量
def outer():
    name = 'fish'
    age = 112
    def inner():
        print(name,age)
    return inner
f = outer()
print(f.__closure__)

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

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


def outer(name):
    def inner():
        print(name)
    return inner
name = 'fish'
f1 = outer(name)
name = 'tom'
f2 = outer(name)

print(f1.__closure__)
print(f1.__closure__[0].cell_contents)
print(f2.__closure__[0].cell_contents)
f1()
f2()

通过我们的实际操作以及输出,我们发现 __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):

        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])
    future.add_done_callback(outer(item[1]))
Pool.shutdown()

总结

通过这篇文章希望大家能够明白什么是Python的闭包,通过代码验证我们可以闭包并不神秘,只是函数对象的一个属性保存了自由变量。
Python的闭包独自使用的情况很少,通常都是和其他的应用一块使用,但是闭包这个思想很重要,面试经常问到。

Original: https://blog.csdn.net/qq_52007481/article/details/127338416
Author: 小鱼干儿♛
Title: [Python进阶]Python闭包的深入浅出

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/673916/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球