Python中装饰器的用法
装饰器的作用
当我们需要为函数拓展新的功能,但是又不能修改函数的内部结构时,就可以通过装饰器来完成。通过装饰器为函数拓展功能符合“对于扩展是开放的,对于修改是封闭的”这一开闭原则。下面我们将通过六个步骤了解如何使用装饰器。
步骤一
我们先定义一个函数f,现在我们需要为其添加运行时打印出当前时间的功能,我们可以通过改变内部代码来完成:
def f(): print('this is f') -------------------------------- import time def f(): print(time.time()) print('this is f') f()
步骤二
步骤一在函数内部添加打印时间代码显然不符合开闭原则。换种思路,我们可以定义一个新函数getTime,将f作为参数传入,执行getTime来实现拓展功能:
import time def f(): print('this is f') def getTime(func): print(time.time()) func() getTime(f)
步骤三
步骤二中使用getTime确实实现了功能拓展,但是我们的本意是为f函数拓展功能,由调用getTime函数来实现显然不够友好。让我们换一种getTime的实现形式:
import time def decorator(func): def wrapper(): print(time.time()) func() return wrapper def f(): print('this is f') d = decorator(f) d()
我们使用了闭包的结构来实现getTime,内部的wrapper函数和原来的getTime函数完全相同。我们先调用decorator将wrapper返回给变量d,然后通过d来调用wrapper实现原来getTime的功能。具体细节可以看Python中的作用域及闭包一文中关于闭包的实现。
步骤四
在步骤三中我们不但需要调用新的变量d来实现功能,并且还增加了代码的复杂度,可以说甚至不如步骤二中的实现。但是当我们使用了@这个语法糖后情况就将会改变:
import time def decorator(func): def wrapper(): print(time.time()) func() return wrapper @ decorator def f(): print('this is f') f()
当我们为f函数打上@decorator装饰器后,我们就可以直接调用f来实现功能,真正实现了对f函数的功能拓展。(@只是一种语法糖,这种方式看似只调用了f函数,实际调用过程与步骤三中相同,最后都是调用了内部的wrapper来实现)
步骤五
在步骤四中我们已经初步实现了装饰器,但如果f函数是一个需要传入参数的函数我们该如何修改:
使用@前:
import time def decorator(func): def wrapper(n): print(time.time()) func(n) return wrapper def f(n): print('this is f' + n) d = decorator(f) d('.')
我们在wrapper函数的定义时加上参数n,在其内部调用f函数时传入参数n,并在外部用d来调用wrapper时传入参数。
使用@后:
import time def decorator(func): def wrapper(n): print(time.time()) func(n) return wrapper @ decorator def f(n): print('this is f' + n) f('.')
加上装饰器后我们直接在调用f函数时传入参数,对于wrapper中的修改与上面相同。
步骤六
步骤五中我们实现了只传入一个参数的函数装饰器,那么如果我们事先不知道被装饰的函数会有几个参数我们该怎样定义装饰器?这就需要使用如下两个变长参数:
- *args:变长参数元组
- **kwargs:变长关键字参数字典
在wrapper中使用这两个变长参数后我们就得到了完整的装饰器,可以用来装饰含有任意参数个数和类型的函数:
import time def decorator(func): def wrapper(*args, **kwargs): print(time.time()) func(*args, **kwargs) return wrapper @ decorator def f(n): print('this is f' + n) @ decorator def f2(n,m): print('this is f2' + n + m) f('.') f2('.','。')