装饰器简单理解就是在多层函数嵌套时用@语法糖来代替一层函数。
1. 无参数装饰器
举常见的两个无参数例子:
(1)打印函数运行时间
(2)日志的打印
1、(打印函数的运行时间)
from time import time
def count(func):
def wrapper(*args , **kw):
start = time()#起始时间
res = func(*args , **kw)
end = time()#结束时间
print('run time is {}'.format(end-start))
return wrapper
@count
def create_list(n):
return [i for i in range(n)]#创建一个从0到n-1的列表
res = create_list(10000000)
"""result: run time is 1.6294074058532715 """
由上述例子可以发现三点:@count等价于count(create_list(n));再不改变create_list()方法就能获得函数的运行时间;@count可以装饰任何函数,获得该函数的运行时间。
2、(日志的打印)
# import functools
def logger(func):
# @functools.wraps(func)
def wrapper(*args ,**kw):
print("开运运行名为 {} 的函数".format(func.__name__))
res = func(*args ,**kw)
print("运行结果为:{}".format(res))
print("该函数运行结束")
return wrapper
@logger
def add(a,b):
c=a+b
print(add.__name__)#wrapper
return c
add(2,5)
"""result:
开始运行名为 add 的函数
wrapper
运行结果为:7
该函数运行结束
"""
这个例子利用无参装饰器实现了简单的日志打印,但发现add函数中:print(add.name) 输出的结果为wrapper,而不是add,那么要如何解决装饰器带来的影响呢?
这时可以调用functools库来解决该问题,实例如下:
import functools
def logger(func):
@functools.wraps(func)
def wrapper(*args ,**kw):
print("开运运行名为 {} 的函数".format(func.__name__))
res = func(*args ,**kw)
print("运行结果为:{}".format(res))
print("该函数运行结束")
return wrapper
@logger
def add(a,b):
c=a+b
print(add.__name__)#add
return c
add(2,5)
"""result:
开运运行名为 add 的函数
add
运行结果为:7
该函数运行结束
"""
从宏观的来看,可以发现在我们定义的装饰器logger中又引入了一个装饰器:@functools.wraps(func);该装饰器的基本功能就是能够还原新传入的函数的原有属性。
再稍微深入探究,查看functools.wraps的源码,发现wraps返回了partial类,而该类实现了复制并保存传入原函数的属性。
更详细的了解点击这里
2. 有参数装饰器
装饰器不仅能传函数,也可以传入其它参数,则此时就需要至少三层嵌套函数来实现装饰器的传参功能。例子如下:
import functools
def log_is_hello(text):
def decorate(func):
@functools.wraps(func)
def wrapper(*args , **kw):
print("该函数名为:{}".format(func.__name__))
print("传入的参数*args为:{}".format(*args))
print("log :{} {}".format(text,*args))
return func(*args ,**kw)
return wrapper
return decorate
@log_is_hello("hello")
def text_is_hello(word):
print(text_is_hello.__name__)
text_is_hello('world')
"""result:
该函数名为:text_is_hello
传入的参数*args为:world
log :hello world
text_is_hello
"""
这咋一看优点复杂,多层嵌套,比较难明白是如何将text_is_hello函数传入的;其实对装饰器进行拆分就容易理解很多了:
decorate = log_is_hello("hello")
wrapper = decorate(text_is_hello)
wrapper("world")
"""result:
该函数名为:text_is_hello
传入的参数*args为:world
log :hello world
text_is_hello
"""
这个流程输出结果一样,更容易明白:
(1)先将参数传入到装饰器中,得到带有"word"的参数的decorate装饰器;
(2)再将函数text_is_hello传入到decorate装饰器中,得到函数wrapper,得到步骤一传入的参数和步骤二传入的函数方法;
(3)调用wrapper方法并传入参数。
3. 无参类装饰器
还可以用类来实现装饰器,该类必须要实现__init__和__call__俩个内置函数。例子如下
class Say(object):
def __init__(self,func):
self.func = func
def __call__(self, *args, **kwargs):
print("开始运行{}函数".format(self.func.__name__))
self.func(*args,**kwargs)
print("函数运行结束")
# return self.func(*args , **kwargs)
@Say
def log_is_call(who):
print("{0}说我的函数名为:{1}".format(who,log_is_call.func.__name__))
log_is_call("Mg")
"""reslut:
开始运行log_is_call函数
Mg说我的函数名为:log_is_call
函数运行结束
"""
由上述例子就可以明白,为什么类必须实现__init__和__call__两个内置函数。其实就是__init__用于获取传入函数方法;__call__用于获取传入函数的参数和做一系列其它操作。这个类装饰器实现的方法不止这一种,往下看就明白了。
4. 有参类装饰器
最常见的类往往需要传入一些参数,例子如下:
class Say(object):
def __init__(self,age = None):
self.age = age
def __call__(self,func):
def wrapper(*args , **kwargs):
print("开始运行{}函数".format(func.__name__))
func(*args, **kwargs)
print("我的年龄是:{}".format(self.age))
print("函数运行结束")
return wrapper
@Say(age = 22)
def log_is_call(who):
print("我的名字叫:{}".format(who))
log_is_call("Mg")
"""result:
开始运行log_is_call函数
我的名字叫:Mg
我的年龄是:22
函数运行结束
"""
看到这个就联想到上面第二种有参的函数装饰器类型,可以和其类比,唯一不同的是其装饰器的参数是在__init__中传入的。
总结
一句话总结,装饰器就是用@前缀+函数名表示,用于不改变函数本身,而是将其放入另一个函数中,这就是装饰器的基本思想。
参考文献
1.https://www.jianshu.com/p/ee82b941772a
2.https://www.jb51.net/article/168276.htm
3.https://zhuanlan.zhihu.com/p/45535784