322-装饰器
闭包的作用就是为了编写装饰器
装饰器的本质就是一个闭包
装饰器定义
就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。
装饰器的功能特点:
①不修改已有函数的源代码
②不修改已有函数的调用方式
③给已有函数增加额外的功能
闭包这么编程装饰器的?
装饰器的雏形(下面都是没有返回值的函数)
栗子1:
在发评论函数前加一个登录的功能
提出需求:在不改变源函数以及源函数调用方式的前提下,为其添加一个权限验证(请先登录)
不改变源函数、不改变原有调用方式、需要增加权限验证(额外功能)
定义一个装饰器:本质:闭包函数(①有嵌套②有引用③有返回),而且装饰器函数本身必须有一个fn参数,代表要装饰的函数名称,当我们调用装饰器的时候,系统会自动将要装饰函数的名称发给fn参数
def logging(fn): # 一般都叫fn
def inner():
# 引用fn函数之前,增加额外功能
print('这里是登录操作')
# 引用局部变量fn
fn()
return inner
@logging # 语法糖🍭
def comment():
print('这里是发表评论操作')
comment() # 执行发表评论函数
当调用语法糖的时候会自动把comment()作为参数传给fn
调试的代码执行顺序
- @语法糖首先被执行
- 执行装饰器函数把要装饰的函数名称传递给fn参数
- 执行装饰器内部的inner函数,表现为执行函数名称comment
- 当装饰器执行fn()的时候,原函数真正被执行
comment()是最后一步执行的
装饰器的原始实现:(现在都是用上面的语法糖来实现)
装饰器调用的本质↓:
(func= logging(comment)
)只不过把func写成comment就和装饰器完全一致了
再调用comment()的时候本质就是调用inner()了
因为装饰器不能改变原来函数的代码和调用方式
所以用语法糖的格式来写
# 定义一个装饰器(①不能改变源代码②不能改变原有函数的调用方式)
def logging(fn):
# fn = comment
def inner():
print('请先登录')
fn()
return inner
def comment():
print('发表评论')
comment = logging(comment) # 给comment添加装饰器,变量名称用同名,这样就不会改变原有调用方式
# comment = Logging() 返回结果 => inner
comment() # 这里的comment已经不是原先的comment了
# comment() => inner()
栗子2:实际应用:求程序执行时间装饰器(装饰器带不定长参数(自己加 的))
在不改变原函数代码和调用方式的前提下,为其添加统计执行时间的功能:可以使用装饰器 -> 有嵌套、有引用、有返回
看这个例子足够了,这里涵盖很多知识点,包括下面的带参数的装饰器知识
import time
def get_time(fn):
def inner(*args, **kwargs): # 装饰器带任意参数
start = time.time()
fn(*args, **kwargs)
end = time.time()
print(f'{fn.__name__}函数执行时间:{end - start:.2f}') # 获取函数名;格式化输出
return inner
@get_time
def ikun(b):
a = []
for i in range(b):
a.append(i * 'ikun')
ikun(10000)
装饰器装饰带有参数的函数for通用装饰器
栗子3:装饰器装饰带有参数的函数,函数几个参数,inner接收几个参数
需求:①在不改变原有函数代码②不改变原有函数调用方式的前提下③为其新增一个输出日志功能
注:实际日志应该写入到日志文件,但是还没学,这里就用print代替
需求:在输出结果之前,添加一个打印日志的功能=>
print('--日志信息:正在努力进行计算--')
def logging(fn):
def inner(a, b):
#添加额外要增加的功能
print('-----日志信息:正在努力进行计算-----')
fn(a, b) #实际执行sum_num函数
return inner
#定义一个函数sum_num(),针对参数求和
@logging
def sum_num(num1, num2):
result = num1 + num2
print(result)
#调用原函数
sum_num(10, 20) #在装饰器的底层,相当于调用inner()
栗子4:进阶:不定长参数的装饰器
def logging(fn):
def inner(*args, **kwargs):
#输出日 志信息
print('-----输出日志:正在努力进行加法运算')
fn(*args, **kwargs)
return inner
#定义源函数
@logging
def sum_num(*args, **kwargs):
sum1 = 0
for i in args:
sum1 += i
for value in kwargs.values():
sum1 += value
print(sum1)
##调用sum_num函数
sum_num(10,20,30,a=40,b=50)
装饰器-装饰带有返回值的函数for通用装饰器
原函数:
def func(num1,num2):
result = num1+ num2
return result
print(func(10,20))
需求:编写一个装饰器,在不改变原函数以及调用方式的基础上,为其添加一个日志功能
# 定义装饰器名称
def logging(fn);
# ①嵌套
def inner(num1, num2):
# 日志输出功能
print('这是日志信息')
# ② 引用
return fn(num1, num2) # fn = func, fn() → func(10,20) → 30
# ③返回
return inner
@logging
def func(num1, num2):
result = num1 + num2
return result
print(func(10, 20)) # func(10, 20) 调用inner(10, 20)
通用装饰器(重点)
重点:通用装饰器=>既可以装饰器带有参数的函数,也可以用于装饰带有返回值的函数
总结口诀:通用装饰器五步走=>①有嵌套②有引用③有不定长参数④有返回值⑤返回内层函数的地址
以后都写通用装饰器
def logging(fn):
def inner(*args, **kwargs):
print('日志')
return fn(*args, **kwargs)
return inner
@logging
def sub_num(num1, num2):
return num1 - num2
print(sub_num(20, 10))
sum是加 sub是减
装饰器传参(带有参数的通用装饰器)
再加一层嵌套用于传参
# 装饰器传参(带有参数的装饰器)
def decoration(flag):
def logging(fn):
def inner(*args, **kwargs):
# 判断是加法还是减法运算
if flag == '+':
print('---正在努力加法运算---')
elif flag == '-':
print('---正在努力减法运算---')
return fn(*args, **kwargs)
return inner
return logging
@decoration('+')
def sum_num(a, b):
return a + b
print(sum_num(1, 2))
@decoration('-')
def sub_num(a, b):
return a - b
print(sub_num(1, 2))
类装饰器
装饰器大多数都是通过函数来装饰函数,其实也可以通过一个类来装饰函数,这种装饰器就称之为”类装饰器”
# 装饰器大多数都是通过函数来装饰函数,其实也可以通过一个类来装饰函数,这种装饰器就称之为”类装饰器”
# 类装饰器规则:
# 1、必须有一个__init__方法,用于接收要修饰的函数
# 2、必须把这个类转换为可以调用的函数 → __call__方法
# 定义一个类装饰器
class Check():
def __init__(self, fn):
self.__fn = fn
def __call__(self, *args, **kwargs):
print('请先登录')
self.__fn()
# 定义一个源函数
@Check
def comment():
print('发表评论')
# 调用原函数
comment()
一个妙哉的装饰器例题
"""
4. 题干
根据如下说明,编写代码完成相关需求
1、
不带装饰器的基础功能:entry_grade
可以完成『成绩录入功能』
1.1可以重复录入成绩,默认所有输入都是合法的(1~100之间的数)
1.2当录入成绩为0时,结束成绩的录入
1.3将录入的成绩保存在列表中并返回给外界,例如:[90, 80, 50, 70]
2、
选择课程装饰器:choose_course
为『成绩录入功能』新增选择课程的拓展功能,达到可以录入不同学科的成绩
2.1可以重复输入要录入的学科名,然后就可以进入该门学科的『成绩录入功能』,录入结束后,可以进入下一门学科成绩录入
2.2当输入学科名为q时,结束所有录入工作
2.3将学科成绩保存在字典中并返回给外界,例如:{'math': [90, 80, 50, 70], 'english': [70, 50, 55, 90]}
3、
处理成绩装饰器:deal_fail
可以将所有录入的成绩按60分为分水岭,转换为 "通过" | "不通过"进行存储
3.1,如果只对原功能装饰,结果还为list返回给外界,例如:["通过", "通过", "不通过", "通过"]
3.2,如果对已被选择课程装饰器装饰了的原功能再装饰,结果就为dict返回给外界,
例如:{'math': ["通过", "通过", "不通过", "通过"],'english': ["通过", "不通过", "不通过", "通过"]}
"""
def choose_course(func):
def inner(*args, **kwargs):
course_dict = {}
while True:
course = input("请输入要录入的学科名:")
if course == "q":
break
course_dict[course] = func(*args, **kwargs)
return course_dict
return inner
def deal_fail(func):
def inner(*args, **kwargs):
result = func(*args, **kwargs)
return ["通过" if grade >= 60 else "不通过" for grade in result]
return inner
@choose_course
@deal_fail
def entry_grade():
result = []
while True:
grade = int(input("请输入成绩:"))
if grade == 0:
break
result.append(grade)
return result
print(entry_grade())