装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
装饰器想实现的功能
例如,想在一个函数执行的时候,记录下函数的执行日志:
原函数:
def foo():
print('i am foo')
添加日志功能后:
def foo():
print('i am foo')
logging.info("foo is running")
但若多个函数都要添加日志功能,会造成代码大量重复,可以定义一个新函数,先处理日志功能,再执行业务函数
def use_logging(func):
logging.warn("%s is running" % func.__name__)
func()
def foo():
print('i am foo')
use_logging(foo)
但是这样就改变了原代码结构,更好的方式就是使用装饰器
简单装饰器
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func() # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
return wrapper
def foo():
print('i am foo')
foo = use_logging(foo) # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于 foo = wrapper
foo() # 执行foo()就相当于执行 wrapper()
语法糖
@符号是装饰器的语法糖,放在函数开始定义之前,可以省略简单装饰器中再次赋值的操作
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func()
return wrapper
@use_logging
def foo():
print("i am foo")
foo()
传递参数
当业务函数需要传递参数时,可以在wrapper函数中指定参数
def use_logging(func):
def wrapper(name):
logging.warn("%s is running" % func.__name__)
return func(name)
return wrapper
@use_logging
def foo(name):
print("i am %s" % name)
foo('TH')
如果不知道有多少个参数,可以使用*args
,可以传递所有参数,如果还有关键字参数,使用**kwargs
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
@use_logging
def foo(name):
print("i am %s" % name)
foo('TH')
带参数的装饰器
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
elif level == "info":
logging.info("%s is running" % func.__name__)
return func(*args)
return wrapper
return decorator
@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)
foo()
该程序实现了在装饰器中指定日志的等级,use_logging实际上是对原装饰器的一个函数封装,并返回一个装饰器
类装饰器
装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活性大、高内聚、封装性等优点。类装饰器主要依靠__call__
方法,当用@形式将装饰器附加到函数上时,就会调用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
使用装饰器极大地复用了代码,但是缺点是原函数的元信息不见了,比如函数的docstring
,__name__
,参数列表等,可使用functools.wraps
装饰器来获取
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'f'
print func.__doc__ # 输出 'does some math'
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
f(1)
装饰器顺序
一个函数可同时定义多个装饰器
@a
@b
@c
def f ():
pass
执行的顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,等效于
f = a(b(c(f)))