17.Python基础篇-闭包、装饰器
函数的进阶—闭包
闭包的定义:嵌套函数,内部函数调用外部函数的变量。满足这个条件就算闭包。
闭包案例演示:
def outer(): a = 1 def inner(): print('inner函数中打印的变量a:',a) # 嵌套函数中使用了外层函数的变量。此时满足了闭包的条件。 return inner # 常用的方式,将inner的内存地址作为返回值,可以在外部直接调用inner,并且inner可以使用外层的变量:a。从而延长了变量a的生命周期 inn = outer() print('打印inn', inn) # 打印结果为inner函数的内存地址 inn() # 内存地址+(),即表示调用inner这个函数 输出内容: 打印inn <function outer.<locals>.inner at 0x0000028217752670> inner函数中打印的变量a: 1
函数的进阶-装饰器
装饰器的作用
主要作用是在不修改原始函数的情况下,为其增加额外的功能。应用于日志功能:记录函数的调用;性能测试:统计函数执行的时间等...
装饰器与闭包的关系
装饰器是闭包的应用,装饰器一定是一个闭包函数,但是闭包并不一定是个装饰器。
逐步完善-装饰器的写法
1.0版本-不使用语法糖的写法(为了便于理解)
# 装饰器的固定写法(不使用语法糖得版本,为了便于理解) # 定义一个装饰器函数 def wrapper(func): # 接收被装饰函数的函数名(内存地址) def inner(*args, **kwargs): # 这俩可变参数可以兼容所有参数类型的函数 print("在被装饰函数之前要做的事情") ret = func(*args, **kwargs) # 调用被装饰函数,并接收返回值 print("在被装饰函数之后要做的事情") return ret # 将被装饰函数的返回值,返回给调用者 return inner # 被装饰的函数 def func(a, b): print("被装饰的函数") print("打印两个参数:", a, b) func = wrapper(func) func(1, 2) # 调用func演示
2.0版本-使用语法糖的写法
# 装饰器的固定写法(使用语法糖) # 定义一个装饰器函数 def wrapper(func): # 接收被装饰函数的函数名(内存地址) def inner(*args, **kwargs): # 这俩可变参数可以兼容所有参数类型的函数 print("在被装饰函数之前要做的事情") ret = func(*args, **kwargs) # 调用被装饰函数,并接收返回值 print("在被装饰函数之后要做的事情") return ret # 将被装饰函数的返回值,返回给调用者 return inner # 被装饰的函数 @wrapper # @wrapper 等价于 func = wrapper(func) 。使用@wrapper语法糖把函数加上装饰器 def func(a, b): print("被装饰的函数") print("打印两个参数:", a, b) func(1, 2) # 调用func演示
语法糖是什么:本质上,语法糖是对已有语法结构的封装或简化,让开发者用更简洁的方式表达复杂的逻辑。(引用于ChatGPT)
在python中有些功能使用了语法糖特性?
- Lambda 表达式
- 装饰器
- 三元表达式
- 解包赋值
3.0版本-使用@wraps装饰器,解决被装饰函数无法获取真实的函数名问题
背景:使用函数名.__name__可以获取函数的函数名。但是如果按照前面的写法,将无法获取被装饰函数的真实函数名,实际拿到的是装饰器函数的方法名。代码说明:
def func1_name(): pass print(func1_name.__name__) # 打印出func1_name函数的函数名 输出结果: func1_name
# 定义一个装饰器函数 def wrapper(func): def inner(*args, **kwargs): print("在被装饰函数之前要做的事情") ret = func(*args, **kwargs) print("在被装饰函数之后要做的事情") return ret return inner # 被装饰的函数 @wrapper def func(a, b): print("被装饰的函数") print("打印两个参数:", a, b) print(func.__name__) 输出结果: inner # func是被装饰的函数,按理说我们的需求打印出的应该是"func",也就是函数本身的方法名。现在的执行结果却是"inner"(装饰器内部嵌套函数的方法名)。 # 为了解决获取不到被装饰函数的真实函数名问题,则需要使用functools中的@wraps装饰器,装饰在装饰器的内部方法上 from functools import wraps def wrapper(func): @wraps(func) # 装饰器内部函数增加@wraps方法装饰后,就支持查看被装饰函数真实的函数名 def inner(*args, **kwargs): print("在被装饰函数之前要做的事情") ret = func(*args, **kwargs) print("在被装饰函数之后要做的事情") return ret return inner # 被装饰的函数 @wrapper def func(): print("被装饰的函数") print(func.__name__) # 打印被装饰函数的函数名 输出结果: func
4.0版本-三层嵌套的装饰器,支持装饰器传参,可以用传入标志位来控制全局的装饰器是否生效
案例演示:
import time FLAGE = True # 标志位。True:使用装饰器,False:不使用装饰器 def timmer_out(flage): # 接收参数FLAGE def warpper(func): def inner(*args, **kwargs): if flage: # FLAGE为True执行装饰代码 start_time = time.time() ret = func(*args, **kwargs) end_time = time.time() print(end_time-start_time) return ret else: ret = func(*args, **kwargs) return ret return inner return warpper
# 此处为两部分:
# 第一部分:@
# 第二部分:timmer_out(FLAGE),一个普通的方法调用。
# 1、程序先执行第二部分timmer_out(FLAGE),此处为一个普通的方法调用timmer_out(),timmer_out返回值为warpper的内存地址。
# 2、再执行@,@后的内容为调用timmer_out方法的返回值,返回值为warpper的内存地址,则此处相当于@warpper。
# 通过上面两步,实现了标志位传参,且不影响装饰器功能的正常使用。进而实现通过简单的方式来控制装饰器开关。
# 第一部分:@
# 第二部分:timmer_out(FLAGE),一个普通的方法调用。
# 1、程序先执行第二部分timmer_out(FLAGE),此处为一个普通的方法调用timmer_out(),timmer_out返回值为warpper的内存地址。
# 2、再执行@,@后的内容为调用timmer_out方法的返回值,返回值为warpper的内存地址,则此处相当于@warpper。
# 通过上面两步,实现了标志位传参,且不影响装饰器功能的正常使用。进而实现通过简单的方式来控制装饰器开关。
# @timmer_out(FLAGE) 这句代码等价于 func1 = timmer_out(FLAGE)
@timmer_out(FLAGE) def func1(): time.sleep(1) print("func1") @timmer_out(FLAGE) def func2(): time.sleep(0.1) print("func2") func1() func2() # 输出结果: # func1 # 1.0057849884033203 # func2 # 0.10775208473205566
一个函数使用多个装饰器的情况下,代码执行顺序说明:
import time def warpper1(func): def inner(*args, **kwargs): print('*****warpper1--befor') ret = func(*args, **kwargs) print('*****warpper1--after') return ret return inner def warpper2(func): def inner(*args, **kwargs): print('*****warpper2--befor') ret = func(*args, **kwargs) print('*****warpper2--after') return ret return inner @warpper1 @warpper2 def func(): print('###func###') func() # 执行结果: *****warpper1--befor *****warpper2--befor ###func### *****warpper2--after *****warpper1--after
画图说明:
4.0版本-完整的装饰器,集合了上面3个版本的各个功能
from functools import wraps FLAGE = True # 标志位。True:使用装饰器,False:不使用装饰器 # 定义一个装饰器函数 def wrapper_out_1(flage): def wrapper1(func): # 接收被装饰函数的函数名(内存地址) @wraps(func) # 装饰器内部函数增加@wraps方法装饰后,就支持查看被装饰函数真实的函数名 def inner1(*args, **kwargs): # 这俩可变参数可以兼容所有参数类型的函数 if flage: # FLAGE为True执行装饰代码 print("在被装饰函数之前要做的事情", wrapper_out_1.__name__) ret = func(*args, **kwargs) # 调用被装饰函数,并接收返回值 print("在被装饰函数之后要做的事情", wrapper_out_1.__name__) return ret # 将被装饰函数的返回值,返回给调用者 else: # FLAGE为false则不执行装饰代码 ret = func(*args, **kwargs) # 调用被装饰函数,并接收返回值 return ret # 将被装饰函数的返回值,返回给调用者 return inner1 return wrapper1 # 与wrapper1代码一样 def wrapper_out_2(flage): def wrapper2(func): @wraps(func) def inner2(*args, **kwargs): if flage: print("在被装饰函数之前要做的事情", wrapper_out_2.__name__) ret = func(*args, **kwargs) print("在被装饰函数之后要做的事情", wrapper_out_2.__name__) return ret else: ret = func(*args, **kwargs) return ret return inner2 return wrapper2 # 被装饰的函数 @wrapper_out_1(FLAGE) @wrapper_out_2(FLAGE) # @wrapper_out_2(FLAGE) 这句代码等价于 func = wrapper_out_2(FLAGE) def func(a, b): print("被装饰的函数") print("打印两个参数:", a, b) func(1, 2) # 调用func演示 # 执行结果: 在被装饰函数之前要做的事情 wrapper_out_1 在被装饰函数之前要做的事情 wrapper_out_2 被装饰的函数 打印两个参数: 1 2 在被装饰函数之后要做的事情 wrapper_out_2 在被装饰函数之后要做的事情 wrapper_out_1