python函数装饰器(装饰器类及装饰器函数)的理解
文章目录
函数作为函数装饰器
一个简单的“执行后打印”装饰器执行流程分析
如果要实现一个装饰器功能,即先运行某个函数,运行后,将函数的返回值使用print打印出来,用函数作为函数装饰器的实现为:
from functools import wraps
def print_after_execution_decorator_function(func):
@wraps(func)
def wrapped_func(*args, **kwargs):
ret = func(*args, **kwargs)
print(ret)
return ret
return wrapped_func
对应的使用是:
@print_after_execution_decorator_function
def work_f(i):
return i+1
当调用work_f
函数的时候,输入10,则会在屏幕上打印出11;
分析其中的具体流程如下:
(1)调用work_f
,实际上调用的是print_after_execution_decorator_function(work_f)
,
这里函数装饰器里的@
可以大致理解为调用其后面紧接的对象的__call__()
方法,参数为下一行中def
后面定义的函数,即:print_after_execution_decorator_function.__call__(work_f)
(2)既然如此,来看print_after_execution_decorator_function
里实现了什么过程,函数的参数是func
,返回值是内部的嵌套函数wrapped_func
,那么在逻辑上有:
wrapped_func == print_after_execution_decorator_function.__call__(work_f)
(3)如果带上参数,逻辑上有:
wrapped_func(*args, **kwargs) == print_after_execution_decorator_function.__call__(work_f)(*args, **kwargs)
结合(1)、(3),可以发现,在调用work_f(*args, **kwargs)
的时候,实际上调用的是wrapped_func(*args, **kwargs)
,而wrapped_func
内部的实现为执行func
函数后,获得函数返回值ret
,并打印,然后正常返回到上层;注意在wrapped_func
里,func
是一个由外层函数传递进来的变量,就是work_f
;这么一来,就完成了所说的功能。
@wraps的作用
在wrapped_func
的前面也有一个装饰器@wraps
,这个装饰器的作用就是把被装饰函数的__name__
属性改回其本身,具体来看,在这个例子里,如果将@wraps(func)
注释掉,并在work_f
里增加一行打印函数__name__
属性的代码,即:
def print_after_execution_decorator_function(func):
# @wraps(func)
def wrapped_func(*args, **kwargs):
ret = func(*args, **kwargs)
print(ret)
return ret
return wrapped_func
@print_after_execution_decorator_function
def work_f(i):
print(work_f.__name__)
return i+1
输入i=10,打印出来有两行内容:
wrapped_func
11
这很容易理解,因为前面说到调用work_f(*args, **kwargs)
的时候,实际上调用的是wrapped_func(*args, **kwargs)
,打印__name__
实际也发生在wrapped_func
执行的过程中;
而如果取消@wraps(func)
的注释,再运行一次,打印出的内容变为:
work_f
11
由此可以看出@wraps
装饰器的作用,就是在调用函数的时候,使被装饰函数的__name__
属性不被装饰器所影响。
类作为函数装饰器
使用类改写“执行后打印”装饰器
前面说到:@
可以大致理解为调用其后面紧接的对象的__call__()
方法,参数为下一行中def
后面定义的函数,也就是说函数装饰器可以为任何带有__call__()
方法的对象,因此,只要在类中定义一个__call__()
方法,类可以作为函数装饰器:
from functools import wraps
class PrintAfterExecutionDecoratorClass:
def __call__(self, func):
@wraps(func)
def wrapped_func(*args, **kwargs):
ret = func(*args, **kwargs)
print(ret)
return ret
return wrapped_func
@PrintAfterExecutionDecoratorClass()
def work_c(i):
return i+1
在还没有调用work_c
函数之前,就已经完成了如下事件:
- 实例化
PrintAfterExecutionDecoratorClass
类的一个匿名对象(执行__init__()
方法) - 执行该匿名对象的
__call__()
方法,将work_c
函数与wrapped_func
进行绑定
可以通过在__init__()
和__call__()
中添加print
输出来证明这一点;
在调用work_c
函数的时候,按照前面叙述的类似的过程,实际调用的是PrintAfterExecutionDecoratorClass
类匿名对象的__call__()
所返回的wrapped_func
函数。
为装饰器添加参数
如果要实现的装饰器功能更为复杂,例如“错误后重试”装饰器,且重试次数需要根据给定的参数来调整,这时就不能使用装饰器函数(因为函数装饰器的__call__()
只能使用需要装饰的函数作为唯一输入),而只能使用装饰器类,并将这些参数在__init__()
中传入:
from functools import wraps
class RetryIfError:
def __init__(self, retry_times):
self.retry_times = retry_times
def __call__(self, func):
@wraps(func)
def wrapped_func(*args, **kwargs):
for i in range(self.retry_times):
try:
ret = func(*args, **kwargs)
return ret
except Exception as e:
print(e)
continue
return None
return wrapped_func
@RetryIfError(3)
def work_r():
a = [].pop()
return a
ret = work_r()
print(ret)
这个函数装饰器在尝试运行被装饰的函数抛出异常后,打印异常信息、重新尝试运行该函数,最多三次,如果仍然失败,则返回None,运行代码后,输出为:
pop from empty list
pop from empty list
pop from empty list
None
如果想更改尝试次数,可以修改@RetryIfError()
的参数,例如尝试五次,则写为@RetryIfError(5)
,输出结果为:
pop from empty list
pop from empty list
pop from empty list
pop from empty list
pop from empty list
None