什么是装饰器
@decorator
def target_func():
...
从字面理解, 装饰器就是用来装饰器目标函数target_func,既然是装饰,就不能修改目标函数执行,因此只能在目标函数target_func的首尾进行操作。因此装饰器适合用来做一些记录函数日志,统计函数执行时间等工作,这些功能在不同的函数中代码一致,因此可以将它抽象出来,统一来写。
装饰器本质也是函数,这个函数的参数是函数。
装饰器早期实现
装饰器的早期(python version < 2.4)实现如下,通过函数调用函数来实现装饰器的功能,但这些的不方便之处就是函数阅读不直观。
import time
def logging_time(func):
start_time = time.time()
func()
end_time = time.time()
print("the execution of the function is {} seconds".format(end_time-start_time))
def hello_func():
print("hello")
logging_time(hello_func)
简单装饰器
利用“@装饰器函数”的语法来实现装饰器功能的示例:
import time
def logging_time(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print("the execution of the function is {} seconds".format(end_time-start_time))
return wrapper
@logging_time
def hello_func():
print("hello")
hello_func()
相比于早期的写法,将计算时间的函数放在了hello_func的实现上,避免了调用者调用logging_time函数
带参数的装饰器
目标函数含有参数
目标函数千千万, 函数参数写不完, 怎么办?用“*args, **kwargs”。
import time
def logging_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print("the execution of the function is {} seconds".format(end_time-start_time))
return wrapper
@logging_time
def hello_func(caller="caller"):
print("hello, {}".format(caller))
hello_func("Caller")
装饰器带有参数
@decorator(params_d)
def target_func(params_t):
...
上述的写法相当于下列这种调用`
decorator(params_d)(target_func(params_t))
拓展开来,当有多个装饰器时:
@decorator_2(params_2)
@decorator_1(params_1)
def target_func(params_t):
...
相当于下列这种调用:
decorator_2(params_2)(decorator_1(params_1)(target_func(params_t)))
examle:
import time
def logging_time(python_version="2"):
def inner_func(func):
print("python version is: {}".format(python_version))
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print("the execution of the function is {} seconds".format(end_time-start_time))
return wrapper
return inner_func
@logging_time(python_version="2.7")
def hello_func(caller="caller"):
print("hello, {}".format(caller))
hello_func("Caller")
基于类实现的装饰器
类装饰器不带参数
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的,因此如果类中重载类__call__函数,这个类也可以用来当作装饰器。
import time
class logging_time(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
start_time = time.time()
self.func(*args, **kwargs)
end_time = time.time()
print("the execution of the function is {} seconds".format(end_time-start_time))
@logging_time
def hello_func(caller="caller"):
print("hello, {}".format(caller))
hello_func("Caller")
类装饰器带有参数
@decorator_cls(params)
def target_func(params_t):
...
相当于decorator_cls(params)(target_func(params_t)), 第一个括号相当于调用类的__init__()函数, 第二个括号相当于调用类的__call__()函数,这样理解就清晰了。
example:
import time
class logging_time(object):
def __init__(self, python_version):
self.python_version = python_version
def __call__(self, func):
print("python version is: {}".format(self.python_version))
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print("the execution of the function is {} seconds".format(end_time-start_time))
return wrapper
@logging_time(python_version="2.7")
def hello_func(caller="caller"):
print("hello, {}".format(caller))
hello_func("Caller")
易错点:错误的函数签名和文档
如果在target_func函数中调用上面的case中如果打印hello_func, 打印的会是wrapper, 究其原因就是上面的的hello_func实际调用的是logging_time(hello_func)。
解决方案是调用functools中的wraps模块解决。
example:
import time
from functools import wraps
def logging_time(func):
@wraps(func)
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print("the execution of the function {} is {} seconds".format(func.__name__, end_time-start_time))
return wrapper
@logging_time
def hello_func():
print("hello")
hello_func()
print hello_func.__name__ # hello_func