python装饰器
装饰器在 python 里占着举足轻重的地位。第一:体现在写代码的时候。第二:体现在面试的时候,大部分公司都很有可能问有关装饰器的问题。
1. 装饰器的原则和作用
原则:开放封闭原则
- 开放:对扩展是开放的
- 封闭:对修改是封闭的
作用:
- 不修改函数的调用方式,但是还想在原来的函数前后添加功能
2. 装饰器的初成
下面我们完成一个计算函数运行时间的功能:
import time
def func():
time.sleep(0.01)
print('我是func函数')
def timmer(f):
start = time.time()
f()
end = time.time()
print(end - start)
timmer(func)
我们封装了一个计算函数运行时间的功能,这样我们每次想计算函数的运行时间。只需要调 timmer 函数,并且直接把自己需要被计算时间的函数传进 timmer 内就可以了。
但是这样我们还是有一个不好的点,就是每次调用都是去调用的 timmer。如果我们想计算 func 函数的运行时间,我们不去调用 timmer,而是每次去调用 func,是不是就完美了。
于是我们就可以在调用函数的时候在前面赋值给 func:
import time
def func():
time.sleep(0.01)
print('我是func函数')
def timmer(f):
start = time.time()
f()
end = time.time()
print(end - start)
func = timmer # 这里的timmer没有括号,调用的是内存地址
func()
# func(func) # 不合理
但是如果这样写,我们底部的 “func()”,实际调用的还是 timmer。timmer 调用需要传一个值。如果我们要传入一个值,还需要把 func 函数传进去(详见以上代码注释)。而现在我们上面定义的变量 func 是 timmer,这样子还是不合理的。所以我们可以写成下面这样:
import time # 第1步
def func(): # 第2步
time.sleep(0.01) # 第11步
print('我是func函数') # 第12步
def timmer(f): # 第3步 f --> func的内存地址
def inner(): # 第5步
start = time.time() # 第9步
f() # 第10步(调用func函数)
end = time.time() # 第13步
print(end - start) # 第14步
return inner # 第6步(返回inner的内存地址)
func = timmer(func) # 第4步:执行timmer(func)
# 第7步:变量func指向inner内存地址
func() # 第8步,func()实际执行inner()
以上写法首先要知道,这是一个 “闭包”。大家都知道代码是从上到下运行的。所以程序执行是这个样子的:
- 导入 time 模块
- 读取 func 函数,并创建一个 func 函数的内存地址
- 读取 timmer 函数,并创建一个 timmer 函数的内存地址
- 执行 func = timmer(func) 等号后面的 timmer(func),这里调用了 timmer,并把 func 函数传进去
- 找到 timmer 函数的内存地址(此时这里的 timmer 函数内的参数 f 指向了函数 func 的内存地址),并读取 timmer 函数内的 inner 函数
- timmer 函数将 inner 返回出去(这里的 inner 没有加括号,返回的是 inner 的内存地址)
- timmer 函数 return 的 inner 内存地址赋值给了 func 变量,这里的 func 指向 inner 的内存地址
- func() 实际上执行的是 inner(),调用 inner 函数
- start = time.time()
- f(),调用 func 函数,执行 func 函数
- time.sleep(0.01)
- print(‘我是func函数’)
- end = time.time()
- print(end - start)
3. 语法糖
在被装饰的函数前面加上 @函数名,就是一个语法糖,等于上面代码块内的 func = timmer(func)
import time
def timmer(f):
def inner():
start = time.time()
f()
end = time.time()
print(end - start)
return inner
@timmer # 语法糖 @装饰器函数名
def func(): # 被装饰的函数
time.sleep(0.01)
print('我是func函数')
# func = timmer(func)
func()
4. 有返回值和参数的语法糖
我们上面还不是完整的装饰器函数,因为我们上面的 func 函数没有返回值和参数。如果有返回值和参数,timmer 装饰器要做出相应的修改
4.1 修改为有返回值的语法糖
import time
def timmer(f): # 装饰器函数
def inner():
start = time.time()
f() # 被装饰的函数
end = time.time()
print(end - start)
return inner
@timmer # 语法糖 @装饰器函数名
def func(): # 被装饰的函数
time.sleep(0.01)
print('我是func函数')
return '我是返回值'
# func = timmer(func)
res = func() # func() --> inner()
print(res)
以上代码实际上返回的是 None ,因为 res = func() 的 func() 实际上是 inner(),而 inner() 并没有返回值,所以我们应该在 inner 内加入返回值:
import time
def timmer(f): # 装饰器函数
def inner():
start = time.time()
res = f() # 被装饰的函数
end = time.time()
print(end - start)
return res
return inner
@timmer # 语法糖 @装饰器函数名
def func(): # 被装饰的函数
time.sleep(0.01)
print('我是func函数')
return '我是返回值'
# func = timmer(func)
res = func() # func() --> inner()
print(res)
因为我们调用 func() 实际调用的是 inner(),所以我们直接在 inner 函数内把传入 timmer 的 func 函数调用并返回出去,就拿到 func 函数内的返回值了
思考
def outer():
def inner():
return 'inner'
inner()
outer()
以上代码能拿到 inner 的返回值吗?
4.2 修改为有参数也有返回值的语法糖
装饰带参数函数的装饰器:
import time
def timmer(f): # 装饰器函数
def inner(para):
start = time.time()
res = f(para) # 被装饰的函数
end = time.time()
print(end - start)
return res
return inner
@timmer # 语法糖 @装饰器函数名
def func(a): # 被装饰的函数
time.sleep(0.01)
print('我是func函数', a)
return '我是返回值'
# func = timmer(func)
res = func(1) # func() --> inner()
print(res)
如果我在以上代码的 func 函数内传入多个参数怎么办?那我们的装饰器内部的 inner 函数的参数也需要改成多个参数:
import time
def timmer(f): # 装饰器函数
def inner(para,para2):
start = time.time()
res = f(para,para2) # 被装饰的函数
end = time.time()
print(end - start)
return res
return inner
@timmer # 语法糖 @装饰器函数名
def func(a,b): # 被装饰的函数
time.sleep(0.01)
print('我是func函数', a,b)
return '我是返回值'
# func = timmer(func)
res = func(1,2) # func() --> inner()
print(res)
如果变成这样子的话,如果我有多个函数,参数数量不一样但都需要用到 timmer 这个装饰器怎么办呢?假如是下面这个样子:
import time
def timmer(f): # 装饰器函数
def inner(para,para2):
start = time.time()
res = f(para,para1) # 被装饰的函数
end = time.time()
print(end - start)
return res
return inner
@timmer # 语法糖 @装饰器函数名
def func(a,b): # 被装饰的函数
time.sleep(0.01)
print('我是func函数', a,b)
return '我是返回值'
@timmer # 语法糖 @装饰器函数名
def func(a): # 被装饰的函数
time.sleep(0.01)
print('我是func函数', a)
return '我是返回值'
# func = timmer(func)
res = func(1,2) # func() --> inner()
print(res)
所以我们的装饰器也需要做出相应的改变,利用 动态参数:” *args “,让 inner 去接收,于是装饰器 timmer 就变成了下面这个样子:
def timmer(f): # 装饰器函数
def inner(*args): # *args拿到的是一个元组
start = time.time()
res = f(*args) # 被装饰的函数
end = time.time()
print(end - start)
return res
return inner
但是上面的写法还是有一个弊端,如果我们按关键字传参怎么办,” *args “ hold 不住啊,所以我们可以变成下面这个样子:
def timmer(f): # 装饰器函数
def inner(*args,**kwargs): # *args拿到的是一个元组
start = time.time()
res = f(*args,**kwargs) # 被装饰的函数
end = time.time()
print(end - start)
return res
return inner
上面的 “ **kwargs “ 用于接收关键字参数。由此可见,args 和 kwargs 一起,可以 hold 住整个世界!
以上就是完整版的装饰器的形成了
def wrapper(f): # 装饰器函数 f是被装饰的函数
def inner(*args,**kwargs):
'''在被装饰函数之前要做的事'''
res = f(*args,**kwargs) # 被装饰的函数
'''在被装饰函数之后要做的事'''
return res
return inner
@wrapper # 语法糖 @装饰器函数名
def func(a,b):
'''函数体'''
return '返回值'
5. 装饰器进阶—带参数的装饰器
问大家一个小问题,如果我想在控制台输出我这个函数的名字怎么办?python其实内部有一个魔术方法提供给了我们:
def func():
print('我是函数')
print(func.__name__) # 查看字符串格式的函数名
思考
def wrapper(f):
def inner(*args,**kwargs):
res = f(*args,**kwargs)
return res
return inner
@wrapper
def func(a):
print('我是函数func,我的参数是'.format(a))
return '啦啦啦'
print(func.__name__)
想一想,以上代码块输出的是什么?
输出的这个是不是不太好?现在是 func 函数传入了 wrapper 装饰器函数,全局上等于没有了 func 函数了,func 函数在 wrapper 装饰器内存着呢。但是我们还有一种办法可以正常使用 func 函数内部定义的所有方法(让控制台输出 func ):
from funtools import wraps
def wrapper(f):
@wraps(f)
def inner(*args,**kwargs):
print('装饰前')
res = f(*args,**kwargs)
print('装饰后')
return res
return inner
@wrapper
def func(a):
print('我是函数func,我的参数是'.format(a))
return '啦啦啦'
print(func.__name__)
print(func(1))
这样子就正常啦。
6. 带参数的装饰器
如果我有500个函数都要被同一个装饰器装饰,我就要 @wrapper 五百遍。等什么时候我不需要了,我要删了,我还需要删 @wrapper 五百遍,就很麻烦。如果我利用带参数的装饰器,那么可以一瞬间控制这些
import time
FLAG = True # 如果有一天我需要删了,直接修改FLAG的值为False
def timmer_out(flags):
def timmer(f):
def inner(*args,**kwargs):
if flags:
start = time.time()
res = f(*args,**kwargs)
end = time.time
print(end - start)
else:
res = f(*args,**kwargs)
return res
return inner
return timmer
@timmer_out(FLAG)
def func():
time.sleep(0.01)
print('我是func函数')
@timmer_out(FLAG)
def func2():
time.sleep(0.01)
print('我是func2函数')
func()
func2()
7. 多个装饰器装饰一个函数
def wrapper1(f): # f --> func
def inner1():
print('1')
f() # func
print('2')
return inner1
def wrapper2(f): # f --> inner1
def inner2():
print('3')
f() # inner1
print('4')
return inner2
@wrapper2 # func = wrapper2(inner1) = inner2
@wrapper1 # func = wrapper1(func) = inner1
def func():
print('我是func函数')
func() # --> inner2()
思考
上面的代码块从上到下的执行过程?