Python 装饰器¶
Python 中的装饰器本质上就是一个函数(或者类,因为也存在类装饰器)装饰器最经典的应用场景就是在不改变原函数(类)的情况下增加其他功能或代码,或者说可以抽离大量与新函数功能无关的可复用代码。
装饰器由来¶
先看个例子:
def origin():
print('This is the origin function')
origin()
>>> This is the origin function
现在有一个新的需求,要记录下函数执行日志,可以直接再函数定义中添加代码
def origin():
print('This is the origin function')
logging.info('origin is running')
>>> This is the origin function
但这样做有一个缺点就是如果有很多类似函数也要增加日志,那么每一函数都要相应修改很麻烦,为了复用代码可以这样
def use_logging(func):
logging.warning('%s is running' % func.__name__)
func()
def origin():
print('This is the origin function')
use_logging(origin)
>>> WARNING:root:origin is running
This is the origin function
但这样破坏了原来的代码结构,每次都要将需要记录日志的函数作为参数传递给 use_logging, 这时候装饰器就应运而生
简单装饰器¶
def use_logging(func):
def wrapper():
logging.warning("%s is running" % func.__name__)
return func() # 把 origin 当做参数传递进来时,执行func()就相当于执行foo(), 虽然 func 可能没有返回值但这样写还是更安全
return wrapper
def origin():
print('This is the origin function')
origin = use_logging(origin)
origin()
>>> WARNING:root:origin is running
This is the origin function
这时,use_logging 就像是把真正的函数 origin 包裹起来了一样,所以叫做装饰器。这个例子中,函数进入和推出时被称为一个切面,所以这也叫做面向切面编程。
@语法糖¶
上面的例子中最后一步赋值可以被省略
origin = use_logging(foo)
origin()
在业务函数定义前面加上 @use_logging, 就可以省略最后一步的赋值过程
def use_logging(func):
def wrapper():
logging.warning("%s is running" % func.__name__)
return func() # 把 origin 当做参数传递进来时,执行func()就相当于执行foo(), 虽然 func 可能没有返回值但这样写还是更安全
return wrapper
@use_logging
def origin():
print('This is the origin function')
origin()
>>> WARNING:root:origin is running
This is the origin function
带参数的装饰器¶
有时装饰器可能也需要参数,可以给装饰器提供很大的灵活性,比如在装饰器中可以制定日志的等级
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warning("%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 origin(name='origin'):
print("This is %s" % name)
origin()
>>> WARNING:root:origin is running
This is origin
这就是允许参数的装饰器,实际上时对原有装饰器函数的再封装并返回一个装饰器,本质上这是一个含参数的闭包。当使用 @use_logging(level='warn') 调用的时候,Python 能发现这一层封装,并把参数传递到装饰器环境中。
@use_logging(level='warn') 等价于 @decorator(带参数)
类装饰器¶
装饰器不仅可以是函数,也可以是类,相比函数装饰器,类装饰器实际上具有更高的灵活性,内聚性和封装性。类装饰器主要依靠类的 call() 方法,当使用 @ 将装饰器附加到函数上的时候,就会调用此方法。
class test_decorator(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@test_decorator
def origin():
print ('This is the origin function')
origin()
>>> class decorator runing
This is the origin function
class decorator ending
functools.wraps¶
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的信息不见了,比如函数的docstring、name、参数列表,先看例子:
# 装饰器
def logged(func):
def with_logging(*args, **kwargs):
'''doc for with_logging'''
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
'''doc for f'''
return x + x * x
>>> f.__name__
>>> with_logging
>>> f.__doc__
>>> doc for with_logging
函数 f 的信息被 with_logging 的信息取代了,于是它的 docstring, name 就变成了 with_logging 函数的信息了。 好在有 functools.wraps,wraps 本身也是一个装饰器,它能把原函数的信息拷贝到装饰器的 func 函数中,这就使得装饰器里的 func 具有和原函数一样的信息了。
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
'''doc for with_logging'''
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
'''doc for f'''
return x + x * x
>>> f.__name__
>>> f
>>> f.__doc__
>>> doc for f
装饰器作用的顺序¶
一个函数可以同时被多个装饰器修饰,装饰器执行的顺序是从内到外的,最先调用内层的装饰器,然后依次到外层
@a
@b
@c
def f(x):
return x+1
# 等价于
f = a(b(c(f)))