参考:
Python 函数装饰器
关于装饰器的理解
装饰器是一个嵌套函数,可以理解为一个加工场所,把需要装饰的函数传递过去,装饰完成之后再返回出来。
举例
假设现在有一个下面这样的函数:
def foo():
print('i am foo')
现在需要给它添加一些额外的功能,比如输出函数的执行日志,那我们可以这样做:
def foo():
print('i am foo')
logging.warning('foo is running')
我们在函数内部添加一句日志代码不就可以了吗?当然可以,但是假如现在还有其他函数也需要添加这句代码,难道要每一个函数都给它重新加几句代码吗?显然,这样是很麻烦的。
那么,我们还可以这样做:
def use_logging(func):
logging.warning('%s is running'%func.__name__)
func()
我们重新定义了一个函数叫做use_logging(func)
,它接收一个函数作为参数,在内部执行日志的输出,之后再执行传递过来的函数。
从功能上来说,这样就实现了我们的需求:
这样做当然是可以的,但是实际上我们每次调用的是use_logging(func)
这个函数,这就破环了原来的代码结构,而且,每次都需要使用use_logging(func)
,并且传递参数,也很麻烦。
那么,有没有更好的方法呢,当然有,那就是装饰器。
一个简单的装饰器
装饰器利用嵌套函数来实现,为什么这样呢,看着代码其实可以体会到。
#定义一个装饰器函数,把你需要装饰(添加额外功能)的函数,作为参数拆传递进去
def use_logging(func):#func是 需要装饰的函数的 形参
#内部的函数实现具体的装饰细节
def wrapper():
logging.warning('%s is running'%func.__name__)
func()
return wrapper
def foo():
print('i am foo')
使用这个装饰器:
foo = use_logging(foo)
foo()
的定义如上,use_logging(foo)
,将foo
指向的函数传递进去,在其内部其实就是将wrapper()
函数返回出来,执行完之后,就相当于foo = wrapper
,其实就是变量之间的赋值,因为函数名也是一种变量。
foo
送进去装饰完之后返回的是另一个函数,但是由于变量之间的相互赋值,将新的函数再次赋值给foo
,下次调用还是使用foo()
,就好像foo()
没变,却添加了额外的功能。
我们看下效果:
确实的实现了需要的效果,同时函数名还没变。
语法糖
语法糖(Syntactic sugar):
- 计算机语言中特殊的某种语法
- 这种语法对语言的功能并没有影响
- 对于程序员有更好的易用性
- 能够增加程序的可读性
简而言之,语法糖就是程序语言中提供[奇技淫巧]的一种手段和方式而已。 通过这类方式编写出来的代码,即好看又好用,好似糖一般的语法。固美其名曰:语法糖。
语法糖还没有仔细了解
@就是装饰器的语法糖,它放在函数开始定义的地方,这样就可以省略最后一步的再次赋值的操作。
函数的定义依旧:
#定义一个装饰器函数,把你需要装饰(添加额外功能)的函数,作为参数拆传递进去
def use_logging(func):#func是 需要装饰的函数的 形参
#内部的函数实现具体的装饰细节
def wrapper():
logging.warning('%s is running'%func.__name__)
func()
return wrapper
#这里有不同
@use_logging
def foo():
print('i am foo')
这时候,使用这个装饰器就不再需要foo = use_logging(foo)
这句代码了,我们就可以直接使用foo()
这样,foo()
函数不需要做任何修改,只需在定义的地方加上装饰器,调用的时候还是和以前一样,如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。
装饰器在 Python 使用如此方便都要归因于 Python 的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。
需要装饰的函数带有参数
假如现在我的函数foo()
需要参数,比如:
def foo(*args):
for name in args:
print('my name is %s'%name)
那我们就可以在定义wrapper()
函数的时候指定参数,给它定义同样的形参:
def use_decorator(func):
#实现装饰的函数
def wrapper(*args):
logging.warning('%s is running'%func.__name__)
func(*args)#给出同样的形参
return wrapper
foo('zs','ls','ww')
@use_decorator
def foo(*args):
for name in args:
print('my name is %s'%name)
所以,其实不论foo(*args, **kwargs)
,是可变参数,或者关键字参数,实现起来都是类似的。
functools.wraps
现在看一个装饰器的例子:
#定义一个装饰器函数
def decorate(func):
#装饰函数
def wrapper():
'''这是内部实现装饰的函数wrapper()注释文档'''
print('装饰语句')
func()
print('装饰语句')
return wrapper
#这是需要装饰的函数
@decorate
def f():
'''这是f()的注释文档'''
print('我是f(),需要装饰')
if __name__ == '__main__':
f()
很符合预期的输出。
接下来我们输出下函数f
的信息:
#定义一个装饰器函数
def decorate(func):
#装饰函数
def wrapper():
'''这是内部实现装饰的函数wrapper()注释文档'''
print('装饰语句')
func()
print('装饰语句')
return wrapper
#这是需要装饰的函数
@decorate
def f():
'''这是f()的注释文档'''
print('我是f(),需要装饰')
if __name__ == '__main__':
f()
print('--------------------------------')
print(f'函数f()的名称{f.__name__}')
print(f'函数f()的注释文档{f.__doc__}')
结果是:函数f
输出的名称以及注释文档全是内部wrapper
函数的。很明显这不是我们想要的,但是为什么会这样呢。
因为在装饰器函数内部实际上是把wrapper函数返回了,也就是
f = decorate(f)
—> f = wrapper
变量f
以及wrapper
都指向的是内部的那个函数对象,所以我们在外部输出的都是wrapper
指向的函数对象的信息。
也就是下面这种情况:
def f1():
'''f1的注释'''
print('i am f1')
def f2():
'''f2的注释'''
print('i am f2')
那么,怎么解决这个问题呢?
需要使用functools.wraps
使用之前需要导入:
from functools import wraps
修改代码为:
from functools import wraps
#定义一个装饰器函数
def decorate(func):
#装饰函数
@wraps(func)#这里是添加的代码
def wrapper():
'''这是内部实现装饰的函数wrapper()注释文档'''
print('装饰语句')
func()
print('装饰语句')
return wrapper
#这是需要装饰的函数
@decorate
def f():
'''这是f()的注释文档'''
print('我是f(),需要装饰')
if __name__ == '__main__':
f()
print('--------------------------------')
print(f'函数f()的名称{f.__name__}')
print(f'函数f()的注释文档{f.__doc__}')
这样就解决了这个问题,区别仅仅是添加了一句:
@wraps(func)