闭包函数、装饰器的原理以及使用
今日内容总结
在昨日的学习中,我们了解了python中,名称空间的知识。知道了在不同空间下,名称的查找顺序是怎么样的。以及函数的多种运用方式。其中我们知道有一种函数的运用方式,是函数名可以作为函数的返回值使用。而今天,就是对这种函数用法的详细介绍了,也就是闭包与解释器。
闭包函数#
闭包函数的简介#
闭包一是利用一种方式实现局部变量的功能,也就是读取函数内部的变量。二是让变量的值始终保存在内存中。它有两大特征。1.闭:定义在函数内部的函数。2.包:内部函数使用了外层函数名称空间中的名字。也就是说,我们要想使用闭包函数,必须满足这两大特征。代码示例:
# 包含闭包函数两大特征的体现
def output():
a = 1
def inner():
print('jason 666',a) # 符合第一,第二条
return inner
a = 666
res = output()
res() # jason 666 1
第二种传参方式
闭包函数的实际应用#
闭包函数是给函数体传参的另外一种方式,而我们传参有两种方式。
第一种传参方式。代码示例:
# 第一种传参方式:形参
def output(name):
print(name)
output('jason')
# 函数体代码需要什么就可以在形参中写什么
第二种传参方式:闭包,代码示例:
def output(name):
def index():
print(name)
return index
res = output('jason')
res()
# 这种方式变量就可以自行改变了
闭包函数使用时需要注意的地方:
1.由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2.闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便使用。
装饰器#
有了闭包函数的概念,我们再去理解装饰器会相对容易一些。python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象(函数的指针)。装饰器函数的外部函数传入我要装饰的函数名字,返回经过修饰后函数的名字;内层函数(闭包)负责修饰被修饰函数。
装饰器简介#
装饰器并不是一个新的知识点 而是由前两天所有的函数知识点整合到一起的产物。所以我们需要记住装饰器的几点属性,以便后面能更好的理解:1.实质: 是一个函数 2.参数:是你要装饰的函数名(并非函数调用) 3.返回:是装饰完的函数名(也非函数调用) 4.作用:为已经存在的对象添加额外的功能 5.特点:不需要对对象做任何的代码上的变动
在经过上面的理论描述,可能感觉还是有点绕。我们通过代码示例来详细了解一下:
# 解释器的原理推导
import time
print(time.time()) # 1647608965.9303854
# 上述的数字是时间戳:1970年1月1日0时0分0秒距离刚刚代码运行间隔的秒数
time.sleep(1) # 让程序原地等待一秒
print('jason nb')
# 这里我们做出一个需求,统一函数的执行时间
import time
def index():
time.sleep(1)
print('Jason nb')
# 给index函数增加了一个统计执行时间的功能
start_time = time.time() # 函数执行之前获取一个时间戳
index()
end_time = time.time() # 函数执行之后获取一个时间戳
print(end_time - start_time) # 两个时间戳的差值就是函数的执行时间
简易版本装饰器#
在上述代码中,我们发现统一了函数的执行时间。但是通过定义其他函数,对index函数进行了功能增加,,不符合装饰器的特征,因为改变了调用方式。为了不改变调用方式的情况下,调用index函数,同时增加功能呢,这里就需要结合之前学过的闭包函数加以改良了。代码示例:
import time
def index():
time.sleep(1)
print('jason nb')
def home():
time.sleep(1)
print('jason handsome')
def get_time(func):
start_time = time.time() # 函数执行之前获取一个时间戳
func()
end_time = time.time() # 函数执行之后获取一个时间戳
print(end_time - start_time) # 两个时间戳的差值就是函数的执行时间
get_time(index)
get_time(home)
#直接给函数体传参的方式无法实现装饰器
# 采用给函数体传参的方式试试
import time
def index():
time.sleep(1)
print('jason nb')
def home():
time.sleep(1)
print('jason handsome')
def outer(func): # 真正的index被outer局部名称空间存储了
def get_time():
start_time = time.time() # 函数执行之前获取一个时间戳
func() # 调用了真正的index函数
end_time = time.time() # 函数执行之后获取一个时间戳
print(end_time - start_time) # 两个时间戳的差值就是函数的执行时间
return get_time
res = outer(index) # 左侧的res就是一个普通的变量名
res() # jason nb 1.008620023727417
# 我们发现,res函数打印了index函数的内容。在上述注释中说明了原因
index = outer(index)
index() # 看似调用的index其实调用的是get_time
home = outer(home)
home() # 看似调用的home其实调用的是get_time
进阶版本装饰器#
上述函数中,我们发现,index的问题是解决了,那么假如,index里面有参数,那么我们还得确定参数后,确定如何传值。其实,我们可以引用之前的可变长参数的概念。代码示例:
import time
def outer(func_name):
def get_time(*args, **kwargs):
start_time = time.time()
func_name(*args, **kwargs)
end_time = time.time()
print(end_time - start_time)
return get_time
# 这样我们就可以接收任何数据类型参数了。
完整版本装饰器#
我们发现如果要修改的函数有返回值的话,我们的装饰器没有返回值,所以就需要将返回值也模拟出来
import time
def outer(func_name):
def get_time(*args, **kwargs):
start_time = time.time()
res = func_name(*args, **kwargs) # 将func的返回值赋值给res
end_time = time.time()
print(end_time - start_time)
return res # 返回res,就是返回原函数的返回值
return get_time
# 这样我们不仅将需要的返回值模拟出来了。并且执行了真正的函数
其实上述的过程,就是帮助我们理解装饰器的过程,包括装饰器实现的功能。在理解过程中,我们最重要的是明白装饰器的用法,以及昨天说过的额,搞清楚当前所在的位置,空间。才能更好的去理解装饰器。
装饰器模板#
编写装饰器其实有一套固定的代码,也就是我们装饰器模板
def outer(func_name): # func_name用于接收被装饰的对象(函数)
def inner(*args, **kwargs):
print('执行被装饰函数之前 可以做的额外操作')
res = func_name(*args, **kwargs) # 执行真正的被装饰函数
print('执行被装饰函数之后 可以做的额外操作')
return res # 返回真正函数的返回值
return inner
装饰器语法糖#
装饰器语法糖,就是为了让我们写装饰器代码更加简洁,并且看上去更加高端一点。代码示例:
def outer(func_name):
def inner(*args, **kwargs):
print('执行函数之前的操作')
res = func_name(*args, **kwargs)
# 额外操作
return res
return inner
@outer # 等价于 index = outer(index)
def index(*args, **kwargs):
print('from index')
# index = outer(index) # 总感觉这一行代码有点low!!!
@outer # 等价于 home = outer(home)
def home(*args,**kwargs):
print('from home')
对比之前的代码看出,我们@的函数名,一般写在被装饰函数的上方。语法糖会将下面紧挨着的函数名作为参数传入装饰器
装饰器修复技术#
本质是为了让装饰器做到跟原函数完全一样,甚至指向的内存地址都一样,写法如下:
from functools import wraps # 固定搭配,写在这里!!!
def outer(func):
@wraps(func) # 固定搭配,写在这里!!!
def inner(*args, **kwargs):
'''被装饰函数前需要增加的代码'''
res = func(*args, **kwargs)
'''被装饰函数后需要增加的代码'''
return res
return inner
@outer
def index():
pass
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人