装饰器
在python中,装饰器是一个可调用的对象,它接受一个函数参数(即需被装饰的函数),然后返回一个新的函数
假如我们有一个名为decor的装饰器函数,有个函数f需要被decor装饰,那么语法如下
@decor
def f():
print("f function")
在上述代码中,其实际上等价于
def f():
print("f function")
f = decor(f)
这样看来,python的装饰器实际上本身就是一个函数,它的特别之处在于它接受一个函数对象作为参数,然后返回另外一个函数
装饰器的定义
定义一个装饰器的方法与一般的函数定义一般无二,区别在于装饰器返回的是一个函数对象,例如,你可以什么都不做,直接返回被装饰的函数
def decor(func):
return func
你也可以在被装饰的函数基础上做一些其他操作,比如,在装饰器内部定义一个函数inner,调用被装饰的函数,做一些其他事情,比如
def decor(func):
def inner():
print("inner function")
func()
return inner
装饰带有参数的函数
在上面这个例子中,函数f不带有任何参数,对于带有参数的函数f,该装饰器就不能正常工作了,那么,如何让装饰器也能装饰带有参数的函数呢
实际上,装饰器返回的是一个函数对象,既然是一个函数对象,我们只需要让这个返回的函数对象支持带有参数即可,我们改变上面的的装饰器,例如
def decor(func):
def inner(a):
print("inner function")
func(a)
return inner
@decor
def f(a):
print("f function, a = %d" % a)
f(12)
inner function
f function, a = 12
在这里,当我们执行f(12)时,实际上是调用了decor内的inner函数,附带的参数12实际上被传递给了inner函数,inner函数首先执行print函数,然后调用真正的f(12)
装饰带有任意参数的函数
为了让我们的装饰器能够支持任意参数,只需要让inner支持任意参数即可,我们装饰器函数看起来是这样子
def decor(func):
def inner(*args, **kwargs):
print("inner function")
func(*args, **kwargs)
return inner
@decor
def f(a):
print("f function, a = %d" % a)
@decor
def g(x, y, lst, dic):
print(x, y, lst, dic)
f(12)
g('ddd', 90, [1, 43, 32], {'a': 434})
让装饰器带有参数
你可以让你的装饰器带有自己的参数,让装饰器本身带有自己的参数可以极大增加灵活性,例如,我可能有多个函数,它们都被相同的装饰器所装饰,但是对被装饰的函数,我想让它们的执行逻辑有些细微的不同,或者是纯粹的将参数作为一个标识,在这种情况下,我们的装饰器看起来是这样子
@w("i am f")
def f(a):
print("f function, a = %d" % a)
为定义这种类型的装饰器,我们只需要在原来的装饰器上包裹一层函数,他接受一个参数param,例如
def w(param):
def decor(func):
def inner(*args, **kwargs):
print("inner function")
func(*args, **kwargs)
return inner
return decor
此时,被装饰的函数f等价于
f = w('i am f')(f)
装饰带有返回值的函数
在上面的所有例子中,被装饰的函数f都是不带返回值的,有时候,我们的确需要被装饰的函数带有返回值,那么怎么做呢
想一下,文章开头的decor装饰器返回的是inner函数,当我们装饰函数f时,实际上是将f重新指向了返回的inner函数对象,所以,如果原来的f本身带有返回值,只需要在inner函数的末尾返回f的结果即可,我们重写decor函数,并将其装饰一个add函数,如下
def decor(func):
def inner(*args, **kwargs):
print("inner function")
result = func(*args, **kwargs)
return result
return inner
@decor
def add(x, y):
return x + y
res = add(1, 23)
print(res)
inner function
24
Python何时执行装饰器
装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时(即 Python 加载模块时)
我们考虑这样一个例子(注:例子来自于《流畅的python》)
registry = []
def register(func):
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
def main():
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main()
在这个例子中,当其作为脚本运行时,结果如下
running register(<function f1 at 0x00000209E8B93CA8>)
running register(<function f2 at 0x00000209E8CE1678>)
running main()
registry -> [<function f1 at 0x00000209E8B93CA8>, <function f2 at 0x00000209E8CE1678>]
running f1()
running f2()
running f3()
如果把它作为模块导入时,结果为
running register(<function f1 at 0x0000016471470F78>)
running register(<function f2 at 0x0000016471470288>)
上面这个例子说明,函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。这突出了 Python 程序员所说的导入时和运行时之间的区别。
在上面这个例子中,如果你不想在导入的时候就将函数append到registry列表中,而只想在被函数被显式调用才被记录到列表中时,可以将装饰器中的内容放到inner函数中,如下
def register(func):
def inner():
print('running register(%s)' % func)
registry.append(func)
return inner
运行结果为
running main()
registry -> []
running register(<function f1 at 0x00000221E7540288>)
running register(<function f2 at 0x00000221E75403A8>)
running f3()
装饰器的叠放
在函数f上,我们可以放置多个python装饰器,例如
@d1
@d2
def f():
print('f')
其等价于
f = d1(d2(f))