python中有一种工具名为Decorator,即装饰器。如何理解装饰器呢,打个比方,内裤可以用来遮羞,但到了冬天他没办法为我们防风御寒,于是我们发明了长裤,有了长裤之后就不在冷了,装饰器就像我们的说的长裤,在不影响内裤作用的前提下,为我们的身子提供保暖的效果。
装饰器本质上就是一个函数,他可以在不影响其他函数正常工作的前提下提供新的功能,常见的装饰器应用场景如“插入日志、性能测试、事务处理”等,装饰器有返回值,他的返回值是一个函数对象。有了装饰器我们可以抽离出大量与函数本身功能无关的重复代码病进行重用,总的来书装饰器就是为已经存在的对象添加新的功能。
聚个简单例子:
def foo():
print('i am foo')
现在我们需要为该函数添加一个新的功能,打印日志,于是我们在函数的末尾添加了一行语句用于打印日志:
def foo():
print('i am foo')
print('2019-03-08')
foo1()和foo2()也有同样的需求,怎么做,再在他们的后面添加一行语句?这样会产生大量的重复代码,所以我们可以定义一个用来处理此类问题的函数:专门用来执行输出日志功能:
def using_logging(func):
logging.warn("%s is running" % func.__name__)
func()
def bar():
print('XXX')
using_logging(bar)
该函数在逻辑上不难理解,但是如果这样改的话,我们就需要将需要生成日志的函数作为参数传递给日志函数。这种方式将会破幻原有的代码,逻辑,原本我们执行bar()函数只需直接调用bar()即可,但现在我们需要将bar传递给use_logging()函数,即using_looging(bar),那么我呢有没有更好的方式呢,答案就是装饰器。
简单装饰器
def use_loggine(func):
def wrapper(*argc,**kw):
logging.warn('%s is running' % func.__name__)
return func(**argc,**kw)
return wrapper
def bar():
print('XXXXXX')
bar=use_logging(bar)
bar()
函数use_logging就是装饰器,他将真正的业务函数包裹在函数中,看起来就行bar被装饰了,调用方式如下,其中@是装饰器的语法糖,在定义函数是使用,避免再一次赋值操作
def use_loggine(func):
def wrapper(*argc,**kw):
logging.warn('%s is running' % func.__name__)
return func(**argc,**kw)
return wrapper
@use_loggine
def foo():
print('XXXXX')
@use_loggine
def bar():
print('XXXXX')
bar()
如上所示,这样我们可以省去bar=use_loggine(bar)这一句了,直接调用bar()即可得到我们想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装,这样我们提高了程序的可重复利用性,病增加了程序的可读性。
装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。
带参数的装饰器
装饰器还有这更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,该装饰器唯一的参数就是执行业务的函数名。装饰器的语法允许我们在调用时,提供其他参数,比如@decorator(a),但该装饰器也会由原来的两层函数,变为三层函数,最外面的哪一层用来传递业务函数的参数,中间那一层用来调用业务函数,最里面那一层用来对原有函数进行‘装饰’,且每一层都必须返回当前层的函数:
>>>def use_logging(level):
def decorator(func):
def wrapper(*argc,**kw):
if(level=='warn'):
print('%s is running ' % func.__name__)
return func(*argc)
return wrapper
return decorator
>>>@use_logging(level='warn')
def foo(name='foo'):
print('i am %s' % name)
>>> foo()
foo is running
i am foo
functools.warps
使用装饰器能够极大程度的服用代码,但也会有一个缺点,那就是会导致原函数的元信息被覆盖,比如__name__,:
def logged(func):
def with_logging(*argc,**kw):
print(func.__name__ +' was call')
return func(*argc,**kw)
return with_logging
@logged
def f(x):
"""does some math"""
return x+x*x
如上所示,我们如果输出该函数的__name__信息,不难发现,此时该函数的名字已经被’with_logging’所替代:
>>> f.__name__
'with_logging'
此时我们便需要用到functools.wraps,wraps本身也是一个装饰器,他能把原函数的元信息复制到装饰器函数中,这使得装饰器函数也能够拥有和原函数一样的元信息了:
>>>from functools import wraps
>>>def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
>>>@logged
def f(x):
"""does some math"""
return x + x * x
>>>print f.__name__
prints 'f'
>>>print f.__doc__ #
>prints 'does some math'