原文链接:Python Decorator
Python有一个有趣的特征叫做装饰器,可以向已存在的代码添加功能。这也被称作元编程(metaprogramming, 编写操纵其它程序的程序,企图在运行时完成部分本应在编译时完成的工作)
Ref:metaprogramming
热身
为了理解装饰器,我们首先应该知道以下几点:
- 一切皆对象
- 我们所定义的名称仅仅是对象的标识
- 函数也是对象
- 不同的名称可以绑定到相同的对象
来看一个例子:
>>> first("Hello")
Hello
>>> second = first
>>> second("Hello")
Hello
上面的例子中,first和second都引用了同一个函数对象。
现在事情开始变得复杂了,函数能够被当做一个参数传递给另一个函数。如果你以前用过map,filter,reduce,那么你可能对此已经有所了解。这些函数将其它函数作为参数,也被称作高阶函数(Higher order functions)。
下面是这类函数的几个例子:
def inc(x):
'''Function to increase value by 1'''
return x + 1
def dec(x):
'''Function to decrease value by 1'''
return x - 1
def operate(funct, x):
'''A higher order function to increase or decrease'''
result = func(x)
return result
像下面这样调用:
>>> operate(inc, 3)
4
>>> operate(dec, 3)
2
而且,一个函数还能返回另一个函数。
>>> def is_called():
... def is_returned():
... print('Hello')
... return is_returned
...
>>> new = is_called()
>>> new()
Hello
>>>
这里,is_returned()是一个嵌套函数,每次我们调用is_called()的时候就返回这个函数。
函数或方法如果能够被调用,那么我们称之为可调用的(callable)。实际上,任何的对象只要实现了__call__()就被认为是可调用的。所以,大多数情况下,一个装饰器就是一个返回可调用对象的可调用对象(A decorator is a callable that returns a callable)。从根本上说,一个装饰器接收一个函数,添加一些功能然后返回,像下面这样:
>>> def is_called():
... def is_returned():
... print('Hello')
... return is_returned
...
>>> new = is_called()
>>> new()
Hello
>>> def make_pretty(func):
... def inner():
... print('I got decorated')
... func()
... return inner
...
>>> def ordinary():
... print('I am ordinary')
...
>>> ordinary()
I am ordinary
>>> # 装饰ordinary函数
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary
在上面的例子中,make_pretty()就是一个装饰器。在赋值的这一步
pretty = make_pretty(ordinary)
函数ordinary()被装饰,返回的函数被赋予一个新的名字pretty。肯一看到,装饰器函数向原函数添加了一些新的功能。就像包装一个礼物一样,装饰器扮演了一个包装纸的角色。被包装对象(盒子里边的礼物)的本来的特性并没有改变,但是现在它变得更好看了(被装饰了)。
通常情况下,我们会装饰一个函数并这样赋值给它:
ordinary = make_pretty(ordinary)
这是普遍的一种做法,Python有特定的语法来简化这种操作。我们可以使用@符号后门见跟上装饰器的名字,把它放在需要装饰的函数的定义上面,像这样:
@make_pretty
def ordinary():
print("I am ordinary")
等价于:
def ordinary():
print("I am ordinary")
ordinary = make_pretty(ordinary)
这仅仅是采用了语法糖来实现装饰器。
带参数的装饰函数
上面介绍的装饰器非常简单,它只适用于装饰不带参数的函数。要是我们想装饰下面这种函数该怎么办呢:
def divide(a, b):
return a/b
这个函数有两个参数,a和b。我们知道,如果我们传入b的值为0,函数将会抛出一个错误
>>> divide(2, 5)
0.4
>>> divide(2, 0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in divide
ZeroDivisionError: division by zero
现在我们使用装饰器来检查这种情况:
def smart_divide(func):
def inner(a, b):
print("I am going to divide",a,"and",b)
if b == 0:
print("Whoops! cannot divide")
return
return func(a,b)
return inner
@smart_divide
def divide(a, b):
return a/b
如果发生除0错误,新的实现将会返回None
>>> divide(2, 5)
I am going to divide 2 and 5
0.4
>>> divide(2, 0)
I am going to divide 2 and 0
Whoops! cannot divide
使用这种方式我们可以装饰带参数的函数。细心的读者可能注意到嵌套函数inner()的参数列表跟被装饰对象的参数列表是一样的。考虑到这种情况,我们设计一个装饰器能够装饰带任意数量参数的函数。在Python中,function(*args, **kwargs)实现了这种魔法,其中args是非关键字参数(位置有序参数,positional arguments)的元组, kwargs是关键字参数的字典。看下面的一个例子:
def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwargs)
return inner
链式装饰器
在Python中可以链式的使用多个装饰器。即是说,一个函数可以被不同的函数装饰多次。仅仅把装饰器依次放在被装饰函数的上面就好了
def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwargs)
return inner
def star(func):
def inner(*args, **kwargs):
print('*' * 30)
func(*args, **kwargs)
print('*' * 30)
return inner
def percent(func):
def inner(*args, **kwargs):
print('%' * 30)
func(*args, **kwargs)
print('%' * 30)
return inner
@star
@percent
def printer(msg):
print(msg)
>>> printer('Hello')
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
上面的语法,
@star
@percent
def printer(msg):
print(msg)
等价于:
def printer(msg):
print(msg)
printer = star(percent(printer))
在链式装饰中,装饰器的顺序是重要的,如果我们交换下装饰器的顺序:
@percent
@star
def printer(msg):
print(msg)
结果会有所不同:
>>> printer('Hello')
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
使用装饰器的一个例子
使用装饰器来计算一个函数调用的时间消耗
from time import time
def timeit(func):
'''Use this decorator to measure execution time of a function.
eg.
@timeit
def yourFunction(args):
dosomething...
'''
def inner(*args, **kwargs):
start = time()
result = func(*args, **kwargs)
elapsed = time() - start
print('Function %s costs time : %10.6f seconds.' % (func.__name__, elapsed))
return result
return inner
@timeit
def testFunc(n):
sum = 0
for i in range(n):
sum += i * i
if __name__ == '__main__':
testFunc(100000000)
输出结果:
Function testFunc costs time : 18.431244 seconds.