函数装饰器
知识点回顾:
- 装饰器的思想:进一步复用
- 函数的装饰器写法
- 注意内部函数的返回值
日常ctrl点进某个复杂的项目,发现函数定义上方有一个@xxx,它就是装饰器。装饰器本质上是一个 Python 函数,可以在不修改原函数代码的情况下,给函数添加额外功能。本质是如果让一个函数具备太多功能,那么他看起来就会比较乱,可读性比较差,如果把其中一部分相同甚至可以复用的功能用一个新的函数来调用,然后让2个函数同时实现,就会做到:
- 进一步封装了函数的一些用法,做到dry原则(don't repeat yourself)
- 使函数更加具有可读性
所以装饰器本身就是函数中调用其他函数,实现先拆分函数,再合并函数的功能
举个例子,假设你有一个函数 prime_nums() ,核心功能是筛选2-10000的质数并输出:
def is_prime(num):
if num < 2:
return False
elif num == 2:
return True
else:
for i in range(2, num):
if num % i == 0:
return False
return True
def prime_nums():
for i in range(2, 10000):
if is_prime(i):
print(i)
prime_nums()
现在要加入计算耗时的功能,一般会这样做:
import time
def is_prime(num):
if num < 2:
return False
elif num == 2:
return True
else:
for i in range(2, num):
if num % i == 0:
return False
return True
def prime_nums():
t1 = time.time()
for i in range(2, 10000):
if is_prime(i):
print(i)
t2 = time.time()
print(f"执行时间:{t2 - t1}秒")
prime_nums()
但如果使用函数装饰器:
# 定义一个装饰器
def display_time(func): # 装饰器函数,接收一个函数func作为参数
def wrapper(): # 定义一个内部函数,在装饰器中wrapper函数是一个常用的函数名,并非强制,约定俗成的
t1 = time.time()
func() # 直接调用原函数(无参数),这里的func()是指装饰器需要修饰的函数,在这里是prime_nums()
t2 = time.time()
print(f"执行时间: {t2 - t1} 秒")
return wrapper # return wrapper是返回函数对象,如果是return wrapper()则是立即执行wrapper函数
def is_prime(num):
if num < 2:
return False
elif num == 2:
return True
else:
for i in range(2, num):
if num % i == 0:
return False
return True
@display_time # 相当于 prime_nums = display_time(prime_nums)
def prime_nums():
for i in range(2, 10000):
if is_prime(i):
print(i)
prime_nums()
装饰函数就是在原函数的基础上套了个壳子wrapper() ,这个壳子里不仅有原函数,还有额外功能的函数实现:
✅ 外层的装饰器display_time函数:确实只是"取名+接收原函数"的入口
✅ wrapper函数:真正的"功能增强实现层",不可或缺
如果原函数需要传入参数并且还要有返回值(比如传入参数maxnum,计算2-maxnum的质数的个数),那么定义装饰器函数的时候也要传入参数和传递返回值:
def display_time(func):
def wrapper(*args, **kwargs): # 接收任意参数
start = time.time()
result = func(*args, **kwargs) # 执行原函数并保存返回值
end = time.time()
print(f"耗时: {end-start}秒")
return result # 返回原函数的结果
return wrapper
这里提一下两个返回 return result 和 return wrapper :
- return wrapper 属于外层display_time函数,在装饰器被应用时执行,返回包装后的函数对象
- return result 属于内层wrapper函数,在被装饰的原函数每次调用时执行,返回原函数的计算结果
作业:
编写一个装饰器 logger,在函数执行前后打印日志信息(如函数名、参数、返回值)
def logger(func):
def wrapper(*args, **kwargs):
print(f'执行函数:{func.__name__},参数为:{args},{kwargs}') # {}内不能使用解包操作符*
result = func(*args, **kwargs)
print(f'函数已执行,返回结果:{result}')
return result
return wrapper
@logger
def calculate_average(*args, **kwargs):
if not args:
return 0
try:
average = sum(args) / len(args) # 这里不用*是因为元组本身就是这两个函数的可迭代对象,
# 比如sum((1,2,3))合法,但sum(1,2,3)不行,因为sum()只接受一个可迭代参数
return average
except TypeError:
print("错误:所有参数必须是数字")
return 0
print(f'[4, 8, 6.5, 15]的均值为:{calculate_average(4, 8, 6.5, 15)}')
print(f'空输入的均值为:{calculate_average()}')
print(f'非数字输入的均值为:{calculate_average("a", "b")}')
执行函数:calculate_average,参数为:(4, 8, 6.5, 15),{}
函数已执行,返回结果:8.375
[4, 8, 6.5, 15]的均值为:8.375
执行函数:calculate_average,参数为:(),{}
函数已执行,返回结果:0
空输入的均值为:0
执行函数:calculate_average,参数为:('a', 'b'),{}
错误:所有参数必须是数字
函数已执行,返回结果:0
非数字输入的均值为:0
收获心得:
函数装饰器真的是第一次接触,新奇
今天终于把昨天讲的 *args 和 **kwargs 这种什么时候解包(加上*)什么时候不解包(不加*)搞清楚了,只在需要将元组或字典拆分为独立参数时才使用*args或**kwargs,其他情况直接使用元组或字典即可