装饰器
-
有什么用?
假如很久之前写的一个函数,现在要添加一个计时的功能,你会怎么做?重新去看一下代码,搞清楚逻辑之后才能动手添加这个新功能,是不是有点繁琐?实际情况下可能会有各种各样新的需求,有没有一种可以不对源代码做更改也能实现新功能的方法呢?就是python的装饰器。 -
在了解装饰器之前,闭包函数是什么?
在Python中一切皆对象,所以在函数内再创建一个函数是完全可以的,如果内部函数里引用了外部函数里定义的对象,此时内部函数被称为闭包函数。闭包函数所引用的外部定义的变量被称为自由变量。
作用:闭包可以将其自己的代码和作用域以及外部函数的作用结合在一起。
举个例子,
def count(): # 外部函数
a = 1
b = 1
def sum(): # 内部函数
c = 1
return a+c # a是自由变量
return sum
顺便引申一下,什么函数可以被称为闭包函数?
- 函数内部定义的函数
- 引用了外部变量但非全局变量
- 是什么?
装饰器的返回值是一个函数对象。
- 本质:函数
- 参数:要装饰的函数名字(非函数调用)
- 返回:装饰完的函数名字(非函数调用)
- 作用:为已经存在的对象添加额外功能
- 特点:不需要对对象做任何代码上的改动
- 应用场景?
比如:插入日志、性能测试、事务处理、权限校验等。
总结一下:装饰器最大的作用就是对于我们已经写好的程序,我们可以抽离出一些雷同的代码组建多个特定功能的装饰器,这样我们就可以针对不同的需求去使用特定的装饰器,这时因为源码去除了大量泛化的内容而使得源码具有更加清晰的逻辑。
以函数添加计时功能为例,
"""函数的函数装饰器"""
import time
from collections import deque
def decorator(func):
def wrapper(*args,**kwargs):
start = time.time()
func()
end = time.time()
print(end-start)
return wrapper
@decorator
def func():
time.sleep(1)
func() # 函数调用
func是要装饰的函数,想用装饰器显示func函数的运行时间,@decorator相当于执行decorator(func),为func函数装饰并返回。再来看装饰器函数decorator,传入参数是func(被装饰函数),返回参数是内层函数。这里的内层函数wrapper,就相当于闭包函数,起到装饰给定函数的作用,*args表示参数以列表形式传入,**kwargs表示参数以字典形式传入。
注意:为了不破坏原函数的逻辑,要保证内层函数wrapper和被装饰函数func的传入参数和返回值类型必须保持一致。
"""类方法的函数装饰器"""
import time
def decorator(func):
def wrapper(me_instance):
start = time.time()
func(me_instance)
end = time.time()
print(end-start)
return wrapper
class Method(object):
@decorator
def func(self):
time.sleep(1)
p1 = Method()
p1.func() # 函数调用
对于类方法来说,都会有一个默认参数self,它实际表示的是类的一个实例,所以在装饰器的内部函数wrapper也要传入一个参数me_instance,表示将类的实例p1传给wrapper
进阶一点的,类装饰器
"""类装饰器"""
class Decorator(object):
def __init__(self,f):
self.f = f
def __call__(self):
print('decorator start')
self.f()
print('decorator end')
@Decorator
def func():
print('func')
func()
call()是一个特殊方法,它可以将一个类实例变成一个可调用对象,要使用类装饰器,必须实现类中的__call__()方法,就相当于将实例变成了方法。
现在我们要讨论一下多个装饰器的运行顺序
def dec1(func):
print('111')
def one():
print('222')
func()
print('333')
return one
def dec2(func):
print('aaa')
def two():
print('bbb')
func()
print('ccc')
return two
@dec1
@dec2
def test():
print('haha')
test()
装载装饰器的过程相当于执行了test=dec1(dec2(test)),此时先执行dec2(test),所以先打印’aaa’,再打印’111’,执行one()函数,打印’222’,遇到func(),执行two()函数,打印’bbb’,遇到func(),执行test(),打印’haha’,再按由近到远的顺序,打印’ccc’,‘333’
所以执行顺序是,aaa–>111–>222–>bbb–>haha–>ccc–>333