在python高性能编程的2.4中,运用到了python修饰器。运用python修饰器的原因是为了修饰一个函数,达到在不改变原函数的情况下实现一些针对函数的操作或功能的目的。主要代表性的包括日志打印、性能测试、事物处理等等。
那么我们为什么要用到函数,举个例子说明,是这样的。
例如我们有如下代码:
def add(a, b):
result = a + b
return result
如果我们想要知道调用该方法时使用的时间开销是多少,那么我们可以这样做。
def add(a, b):
start = time.clock()
result = a + b
end = time.clock()
print 'Time cost:', (end - start)
return result
那么问题来了,如果我们不止关注这一个函数的时间开销呢?例如我们还有一个名为minus的函数,我们是不是也需要copy同样的代码去复写方法呢?显然不是,这样我们每个代码都需要copy,会造成大量的重复代码,python再也无法优雅。
我们了解到,python万物皆对象,python的函数本身也可以作为对象,那么我们或许可以这样,写一个函数,这个函数计算传入函数的调用时间,也就是这样:
def add(a, b):
result = a + b
return result
def measure(func, *args):
start = time.clock()
func(*args)
end = time.clock()
print 'Time cost:', end - start
measure(add, 1, 2)
然后我们通过如上的调用方法,去计算函数的时间开销。但是问题是,如果我们是临时起意,需要增加这样一个函数,去计算各个函数的开销,我们同样需要大量复写代码,同时,特别的是,在调用的时候,我们将使用新的函数名measure替代原本的add,造成混乱。
那么问题来了,我们能不能在不改变调用函数名,基本不改变原代码结构的情况下,尽可能少的去改动代码,提高维护性能呢?答案是可以的,我们还可以这样。
def add(a, b):
result = a + b
return result
def time_measure(fn):
def wrapper(*args):
start = time.clock()
fn(*args)
end = time.clock()
print 'Time cost:', end - start
return wrapper
add = time_measure(add)
add(1, 2)
我们在time_measure函数下构建一个函数,这个函数用来为fn方法增加一个计时功能,然后返回这个函数给fn,完成重新赋值。
这样,我们可以实现为add方法增加计时功能的同时,不需要更改代码中调用add的部分代码,只需要在添加这个新增功能的函数
time_measure之后,在需要增加计时功能的代码前加上fn = time_measure(fn)这样一行代码即可,这个time_measure函数,就是所
谓的修饰器Decorator,简单易懂,它是用来修饰函数,给函数增加功能的一种函数。
至此,我们已经没法再精简代码,python给了我们一种更加方便的功能,让代码更加精简,那就是使用@符。这个符号的功能
就是将之后的函数作为参数传入@之后的函数,完成修饰功能。使用方法如下。
def time_measure(fn):
def wrapper(*args):
start = time.clock()
fn(*args)
end = time.clock()
print 'Time cost:', end - start
return wrapper
@time_measure
def add(a, b):
result = a + b
return result
add(1, 2)
这段代码和之前的代码功能完全一样,只是更加方便一些。到这里,我们对于修饰器已经有了比较完整的了解,那么,wraps是
什么呢?我们什么时候会用到wraps?
wraps是python自带的functools包里的一个函数。作用是将例如上面的修饰器中的fn函数的函数名,docstring,也就是__name__,
__doc__等属性暴露给fn的调用者。因为实际上经过修饰器修饰的函数,已经是一个新的函数了,有自己的__name__和__doc__,我
们只是想对这个函数做修饰,而并不想改变这些基本属性,这时我们就可以使用wraps。使用方法如下,我们输出__doc__看看有什
么区别。
def time_measure(fn):
@wraps(fn)
def wrapper(*args):
'''ths is decorator'''
start = time.clock()
fn(*args)
end = time.clock()
print 'Time cost:', end - start
print __doc__
return wrapper
@time_measure
def add(a, b):
'''this is add function'''
result = a + b
return result
add(1, 2)
print add.__doc__
此时控制台输出是:
Time cost: 1.18423966484e-06
this is add function
那么如果我们不用@wraps函数呢?
def time_measure(fn):
def wrapper(*args):
'''ths is decorator'''
start = time.clock()
fn(*args)
end = time.clock()
print 'Time cost:', end - start
print __doc__
return wrapper
@time_measure
def add(a, b):
'''this is add function'''
result = a + b
return result
add(1, 2)
print add.__doc__
此时输出则是:
Time cost: 1.57898621979e-06
ths is decorator
代码时间不算,可以看到,同样是输出add.__doc__,没加@wraps时输出的是wrapper中的this is decorator,而加了@wraps后
输出的则是原本add中的this is add function。@wraps的作用就在于此。同理,如果输出的是add.__name__呢?加了@wraps会得到
add,而不加则会得到wrapper。