- 💂 个人主页:风间琉璃
- 🤟 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主
- 💬 如果文章对你有
帮助
、欢迎关注
、点赞
、收藏(一键三连)
和订阅专栏
哦
前言
一般来说闭包这个概念在很多语言中都有涉及,本文主要谈谈python中的闭包定义和相关用法以及装饰器。Python中使用闭包主要是在进行函数式开发时使用,而装饰器(语法糖)
在闭包的基础上为函数添加额外的功能
,并且在不改变原函数代码的情况下。
一、闭包
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量
能够保持其持久性。 —— 维基百科
闭包:在一个内部函数
中,对外部作用域的变量
进行引用
,(并且一般外部函数的返回值为内部函数
),那么内部函数就被认为是闭包。当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包, 可以这样理解,闭包就是能够读取其他函数内部变量的函数。
在函数f1中定义了一个f2函数,f2访问了外部函数f1的变量,并且f1返回了f2函数,在python中是可以返回一个函数的。在上面中a其实就是一个函数,a就是f2。
如果调用函数a,得到的结果是传入参数的整数值加,所以结果为2。一个函数中若要用到另一个函数的参数,则可以通过闭包的形式来实行,闭包,即封闭,包含,内层函数中定义的变量是无法在外层函数进行使用的,即在f2中可以调用x,但是不能在f1函数中调用y。此外可以直接调用函数f1(),但是不能调用函数f2()。
小结:
1.闭包定义是在函数内再嵌套函数。
2.闭包是可以访问另一个函数局部作用域中变量的函数
。
3.闭包可以读取另外一个函数内部的变量。
4.闭包可以让参数
和变量
不会被垃圾回收机制回收
,始终保持在内存中(而普通的函数调用结束后 会被Python解释器自动释放局部变量)
二、装饰器
装饰器是Python中一种非常有用的语法,它可以在不改变原函数代码的情况下,为函数添加额外的功能
。装饰器可以用于日志记录、性能测试、事务处理、缓存等方面。通过装饰器,我们可以将这些功能与原函数分离开来,使得代码更加简洁、易于维护和复用。
对于装饰器的定义,基于函数闭包的形式来实现,即可以将某一个函数作为参数传递给另一个函数,在这另一个函数中去为函数添加功能
. 与普通函数相比,其参数是一个函数,并且返回值是一个函数。
举个例子,我们可以使用装饰器来记录函数的运行时间,代码如下所示(注:下面代码中的@timer就是一个装饰器函数):
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print('函数 %s 运行时间为 %s 秒' % (func.__name__, end_time - start_time))
return result
return wrapper
@timer
def my_func():
time.sleep(2)
my_func()
上述代码中,我们定义了一个装饰器函数timer,它接受一个函数作为参数,并返回一个新的函数wrapper。wrapper函数中,我们记录了函数的开始时间和结束时间,并计算出函数的运行时间。最后,我们将运行时间打印出来,并返回原函数的结果。在使用装饰器时,我们只需要在原函数的定义前加上@timer即可。
下面再来看看装饰器的原理
代码原理及执行循序如下:
(1)当运行该程序代码时,执行到@decorato
r时,就会去调用decorator函数
,发现该函数是一个装饰函数,向下继续执行。
(2)这里被装饰的函数func1会作为参数传递给decorator函数,即decorator中的参数func=func1。
(3)然后开始执行内部函数wrapper,先是打印语句,然后是func函数,因为刚刚把func1传递给func,所以这里执行的是func1(),但该处并不是把func1函数下的代码运行出来,而是decorator函数返回值
是内部函数wrapper,该处返回的是加了装饰器的func1函数,原始函数func1函数的地址已经指向了decorator.wrapper
的函数地址。
(4)程序执行@decorator后,再执行func1(),func1会指向decorator.wrapper的函数地址,所以代码输出结果为:执行decorator.wrapper函数,先打印“我喜欢你”,然后执行func()也就是被当成参数传进来的原始函数func1,打印“我也喜欢你”。经过上述步骤,func1就被装饰了。
对于两个装饰器来说,执行顺序如下:
程序运行后,依次运行@decorator2,@decorator1:
(1)程序运行后,首先运行到@decorator2时,发现是一个装饰器函数,需要对下面的函数进行装饰。因此向下执行寻找decorator2中的参数func,但是下一行是@decorator1,不是一个函数名,是一个装饰器函数。此时,@decorator2暂时执行,执行接下来的装饰器@decorator1,然后向下执行找到函数名func。
(2)func作为参数传给装饰器@decorator1,@decorator1开始执行,打印"装饰器1开始对原始函数进行装饰", 然后@decorator1内部函数开始对func进行装饰,装饰之后,decorator1返回值为内函数wrapper。此时,原始函数func已经指向了decorator1的内函数wrapper的地址,即func = deco1.wrapper。
(3)然后返回继续执行@decorator2,把新的func(@decoratoe1装饰之后的func)作为参数传给decorator2函数。
(4)执行decorator2函数,打印"装饰器2开始对函数进行装饰",然后@decorator2内部函数开始对新的func进行装饰,装饰之后,decorator2返回值为内函数wrapper,此时,新函数func又指向了decorator2的内函数wrapper的地址,即func = deco2.wrapper。经过上述步骤,@decorator2和@decorator1都已经运行,并且func函数已经被两个函数进行装饰了。
(5)然后继续执行 ret = func(), 即运行原始函数func函数,由于func经过装饰后,指向的是decorator2.wrapper,从而运行decorator2.wrapper函数,因此打印出“装饰器2内函数开始执行”,同时继续执行代码:return " [002] " + func() + " [002] “,即返回一个” [002] " + func() + " [002] “,此时返回值中的func()最外层已经被装饰[002]。语句中的func()执行的时寻找到的是func的上一级地址decorator1.wrapper(decorator2.wrapper地址已经使用了)。返回值中的func()即decorator1.wrapper运行后的返回值:” (001) " + func() + " (001) “。 因此decorator2.wrapper函数返回的实际是:” [002] " + " (001) " + func() + " (001) " + " [002] "。
(6)decorator1.wrapper运行时,首先打印“装饰器1内函数开始执行”,同时继续执行代码:return " (001) " + func() + " (001) “。这时语句中的func()才是指向的原始函数func的地址,执行原始函数func,打印出“func函数执行”。原始函数func的返回值是:“Hello Decorator”。因此最终func函数运行后的返回值是:” [002] " + " (001) " + “Hello Decorator” + " (001) " + " [002] "。最后执行print(ret)即打印出经过装饰之后func函数的返回值
总之,装饰器装饰的时,从最靠近原始函数开始进行装饰,逐级向外进行(外函数),所有装饰函数装饰结束后,再执行原始函数,此时原始函数指向的是最后一个装饰器内函数的地址,内函数又从外面的装饰器开始执行,逐级向里面执行(内函数),所有内函数开始执行完了,最后开始执行原始函数下的代码。先里后外,再外后里,最后原始。
多个装饰器装饰器函数运行顺序:
(1)最靠近原始函数的装饰器首先运行,然后运行靠近该装饰器的第二个装饰器
依次启动完所有的装饰器,运行完所有装饰器中的外函数,
(2)然后启动主程序,内函数从外层向里层运行。注意,如果装饰器中内函数前面没有外函数,还是先运行所有的装饰器,然后运行原始函数,
装饰器总结
(1)对原函数功能的补充
:日志记录
(2)对原函数功能的调整
:利用原函数运行结果,再次运算产生新的结果
(3)对原函数功能的重写
:仅使用原函数名
结束语
感谢阅读吾之文章,今已至此次旅程之终站 🛬。
吾望斯文献能供尔以宝贵之信息与知识也 🎉。
学习者之途,若藏于天际之星辰🍥,吾等皆当努力熠熠生辉,持续前行。
然而,如若斯文献有益于尔,何不以三连为礼?点赞、留言、收藏 - 此等皆以证尔对作者之支持与鼓励也 💞。