简介
前面我们看到,在不改变原始函数定义的情况下,可以通过偏函数给函数传入指定的参数,从而简化调用。
能否不改变原函数体内容对函数的功能进行扩充呢?答案是yes,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
先看一下简单的add函数例子
def add(x,y):
print(x,y)
return x+y
print(add(1,2))
1 2
3
现在让我们为add增加一些功能,比如每次调用的时候输出一行语句。
def log(func):
def wrapper(*args,**kwargs):
print("函数%s被调用了" %func.__name__)
return func(*args,**kwargs)#注意此处先执行func函数,再将其返回值作为wrapper的返回值
return wrapper
@log
def add(x,y):
print(x,y)
return x+y
print(add(1,2))
print(add(3,4))
函数add被调用了
1 2
3
函数add被调用了
3 4
7
让我们来分析以上新增的代码。
先来看一下外层的装饰器,装饰器接收一个函数作为参数(func)。
装饰器log的内部嵌套定义了另一个函数wrapper,内函数中引用装饰器的参数(即函数func),并且装饰器返回的是内函数(wrapper),构成了一个闭包。
再看内层,内函数wrapper形参定义为(*args,**kwargs)从而接收任意类型的参数。
函数体内完成新功能(即print(“函数%s被调用了” %func.name)),然后调用被装饰的函数(即 return func(*args,**kwargs))
在被装饰函数add的前面添加语句@log后,相当于执行了
add=log(add)
函数的执行流程变成了先执行装饰器内新增的功能,即 print(“函数%s被调用了” %func.name)
然后通过 return func(*args,**kwargs)执行了add函数。
被装饰函数name
函数对象有一个__name__属性,可以拿到函数的名字。
让我们来看一下被装饰后add的函数名(),此时函数名变成了wrapper。这是因为执行了add=log(add)后,同名的add变量指向了新的函数,于是调用add()将执行新函数,即在log()函数中返回的wrapper()函数。
print(add.__name__)
wrapper
如果希望被装饰函数保留原始函数名,可以在装饰器的内函数前添加另外一个装饰器:@functools.wraps(装饰器参数名)
import functools
def log1(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print("函数%s被调用了" %func.__name__)
return func(*args,**kwargs)
return wrapper
@log1
def add(x,y):
print(x,y)
return x+y
print(add1(1,2))
print(add1.__name__)
函数add1被调用了
1 2
3
add1
装饰器传参
把装饰器应用到被装饰函数时,还可以传递参数。此时需要在原装饰器上再包一层装饰器,即3层嵌套的装饰器。
此时@log2(‘2019’,‘11’,‘21’),相当于执行了语句add=log2(‘2019’,‘11’,‘21’)(add2)
上面的语句首先执行log2(‘2019’,‘11’,‘21’),返回的是decorator函数,再调用返回的函数,参数是add2函数,返回值最终是wrapper函数。
import functools
def log2(year,month,day):
def decorator(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print("%s-%s-%s,函数%s被调用了" %(year,month,day,func.__name__))
return func(*args,**kwargs)
return wrapper
return decorator
@log2('2019','11','21')
def add2(x,y):
print(x,y)
return x+y
print(add2(1,2))
2019-11-21,函数add2被调用了
1 2
3
装饰器作为高级Python工具,能够最小化繁冗的扩展代码,并且有助于确保正确的API使用。
装饰器是设计模式的一种,被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。