装饰器(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!以上就是对装饰器作的简单介绍。另附几个文档和博客,本文部分内容是参考它们的。
廖雪峰的官方网站
wiki python文档
12步轻松搞定装饰器
python--装饰器(decorator)解析
转载于:https://www.cnblogs.com/excellent-ship/p/9093191.html
相关资源:数据结构—成绩单生成器