装饰器(decorator)是一种软件设计模式,装饰器可以在不改变原函数的代码的情况下改变原函数的功能。本文对Python装饰器的基本用法作一扼要说明。
一个简单的例子
下面的一个函数返回两个数相加后的结果:
def add(x, y):
'''
add two numbers
'''
return x + y
例如:
>>> z = add(-8, 9)
>>> print(z)
1
现在我想把输入的数字先转化成绝对值,然后再相加,该怎么办?很简单,直接更改add(x, y)
函数的代码为:
def add(x, y):
'''
add two numbers
'''
if x < 0:
x = -x
if y < 0:
y = -y
return x + y
但是如果我不想改动原来的函数呢?这时候就要用到装饰器了。我们先写下如下的装饰器代码:
def abs_add(func):
def make_change(x, y):
if x < 0:
x = -x
if y < 0:
y = -y
return func(x, y)
return make_change
从上面的代码可以看出装饰器abs_add(func)
其实也是一种函数,只不过看起来稍微奇怪点。从整体上来看,它接收一个函数参数,这个函数参数就是我们要对其进行‘装饰’的函数,‘装饰’被输入的函数也就是对这个函数进行功能上的改造,最后返回一个函数make_change(x, y)
函数,这个函数也就是改造之后的函数了。我们现在利用abs_add(func)
装饰器来对原有的add(x, y)
函数进行改造:
>>> add = abs_add(add)
>>> z = add(-8, 9)
>>> print(z)
17
可见我们利用装饰器实现了我们想要改变的功能,却并没有改动原函数的代码,这就是装饰器最基本最简单的用途。另外说明一点,使用装饰器一般不采用add = abs_add(add)
这种写法,我们一般用如下的方式来使用,这种写法完全与add = abs_add(add)
等价:
@abs_add
def add(x, y):
'''
add two numbers
'''
return x + y
函数名称等属性的变化
一个函数在经过装饰器修饰之后,它的名字会发生变化。举例来说,如下函数是实现把一个数字乘以2倍并返回的:
def double(x):
'''Double a number.'''
return 2 * x
现在再写一个简单的装饰器,该装饰器不对原函数作任何修改:
def decorator(func):
def you_will_see_this_name(*args, **kwargs):
'''Haha! your name has been changed, you_will_see_this_name!'''
return func(*args, **kwargs)
return you_will_see_this_name
我们运行:
@decorator
def double(x):
'''Double a number.'''
return 2 * x
print(double(2))
输出结果是:
4
看起来没有对原函数做了些什么,原函数仍旧输出数字的二倍,但是我们要说的不是这个,而是,double(x)
函数的名字已经不是double了:
>>> double.__name__
'you_will_see_this_name'
可见已经变成装饰器里面you_will_see_this_name(*args, **kwargs)
函数的名字了!再看函数的文档说明,原double(x)
函数的说明是'Double a number.',但是我们看到:
>>> double.__doc__
'Haha! your name has been changed, you_will_see_this_name!'
可见文档说明也被改变了,函数的这些属性的改变可能会对函数接下来的使用造成一定的麻烦,我们能否保持函数的这些属性不变呢?是可以的。我们可以写一个‘装饰器的装饰器’,对已有的装饰器进行功能上的改造,把原来函数的一些属性都复制到新的函数上面去:
def decorator_of_decorator(decorator):
def new_decorator(f):
g = decorator(f)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
g.__dict__.update(f.__dict__)
return g
return new_decorator
现在我们再重复上面的代码,只不过在装饰器头顶上添加了一个@decorator_of_decorator
:
@decorator_of_decorator
def decorator(func):
def you_will_see_this_name(*args, **kwargs):
'''Haha! your name has been changed, you_will_see_this_name!'''
return func(*args, **kwargs)
return you_will_see_this_name
@decorator
def double(x):
'''Double a number.'''
return 2 * x
现在我们就会发现函数的属性已经不再发生变化了
>>> double.__name__
'double'
>>> double.__doc__
'Double a number.'
可是新的问题来了,你对已有的装饰器进行了改造,那么被改造的装饰器不也遭遇了被改造的函数一样的问题了么:属性变了。例如我们来看:
>>> decorator.__name__
'new_decorator'
可见装饰器decorator(func)
的名字已经变成了新的名字,同样地,我们如何避免呢?只需在decorator_of_decorator(decorator)
中添加几行代码即可:
def decorator_of_decorator(decorator):
def new_decorator(f):
g = decorator(f)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
g.__dict__.update(f.__dict__)
return g
new_decorator.__name__ = decorator.__name__
new_decorator.__doc__ = decorator.__doc__
new_decorator.__dict__.update(decorator.__dict__)
return new_decorator
现在我们要是重复以上过程,就会发现不论是被装饰的函数还是被装饰的装饰器,它们的属性都不会被改变了。但是实际应用中真的要这么麻烦么?实际上已经有现成的functools
库可以帮助我们完成上述任务,为实现函数属性不被改变的目的,只需在原装饰器里添加一行代码即可:
import functools
def decorator(func):
@functools.wraps(func)
def you_will_see_this_name(*args, **kwargs):
'''Haha! your name has been changed, you_will_see_this_name!'''
return func(*args, **kwargs)
return you_will_see_this_name
@decorator
def double(x):
'''Double a number.'''
return 2 * x
接下来double(x)
函数的属性就不会发生变化了。
带参数的装饰器
如果需要对装饰器传入参数,那么就需要定义一个返回装饰器的高阶装饰器,类似于我们在上一节已经见过的‘装饰器的装饰器’。举例来说,倘若你想把一个函数重复执行若干次,怎么用装饰器来实现呢?比如你想把下面的hello()
函数重复执行多次:
def hello():
'''print hello world'''
print('Hello world!')
可以编写的装饰器如下所示:
import functools
def run_times(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
'''repeat func several times'''
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
其中times
为函数要重复执行的次数,run_times(times)
装饰器就是一个‘装饰器的装饰器’,因为它对装饰器进行修饰然后返回。运行如下代码我们就可以把hello()
函数重复执行三次:
@run_times(3)
def hello():
'''print hello world'''
print('Hello world!')
结果如下:
Hello world!
Hello world!
Hello world!
以上就是对装饰器作的简单介绍。另附几个文档和博客,本文部分内容是参考它们的。