引言
Python有一个名为decorator的有趣特性,可以在现有代码基础上添加功能,即进行装饰。这种方式也称为元编程(metaprogramming ),因为程序的一部分试图在编译时修改程序的另一部分。
预备知识
第一, 在学习Decorators之前,我们需要有个概念:Python中一切都是对象。定义的名称只是绑定到这些对象的标识符。函数也不例外,也是对象(带有属性)。可以将不同的名称绑定到同一个函数对象。举例如下:
def first(msg):
print(msg)
first("Hello World")
second = first
second("Hello World")
2个函数的输出结果都是Hello World
。这里first
和second
指向同一个函数对象。
第二,我们需要了解函数可以作为参数传递给另一个函数,比如在map
和filter
函数的使用。这种以其他函数作为参数的函数也称为高阶函数。下面是一个示例函数:
def inc(x):
return x + 1
def dec(x):
return x - 1
def operate(func, x):
result = func(x)
return result
print(operate(inc,10))
print(operate(dec,10))
输出结果如下:
11
9
第三,我们看看函数返回另一个函数的情况(在之前介绍闭包的时候也有涉及):
def is_called():
def is_returned():
print("Hello World")
return is_returned
new = is_called()
new()
输出结果:
Hello World
这里,is_returned()
是一个嵌套函数,该嵌套函数在调用is_called()
的时返回。
第四,需要了解闭包,具体可以参考之前关于闭包的博文。
装饰器Decorator
函数和方法可以被调用。事实上,任何实现__call__()
方法的对象都被称为可调用对象。因此,在最基本的意义上,装饰器Decorator是一个返回一个可调用对象的可调用对象。换一句话说,装饰器Decorator本身是一个可调用对象,它返回的结果是一个可调用的对象。
装饰器decorator接受一个函数,添加一些功能并返回它。示例如下:
def make_pretty(func):
def inner():
print("这是装饰后的我")
func()
return inner
def ordinary():
print("我是初始纯天然,不加修饰的")
# ordinary()
# 对原始函数进行装饰
pretty = make_pretty(ordinary)
pretty()
输出结果如下:
这是装饰后的我
我是初始纯天然,不加修饰的
在这里,make_pretty()
是一个装饰器,其装饰步骤如下:
pretty = make_pretty(ordinary)
函数ordinary()
被装饰并返回名为pretty
的函数。
从上述示例可以看出装饰器函数是在原始函数基础上添加一些新功能。这就像包装礼物一样。装饰器充当包装器,被装饰的物品(里面是真正的礼物)的性质不会被改变。但经过包装装饰后,它看起来更漂亮了。一般可以通过下面方式装饰一个函数,再把它重新分配给另一个函数:
ordinary = make_pretty(ordinary)
这是一种常见的结构,Python有一个专门的语法来简化它。可以把@
符号和装饰器函数的名称一起使用,并把它放在要装饰的函数的定义之上。这是实现装饰器的语法糖。比如上述的装饰器可以用如下方式表现:
@make_pretty
def ordinary():
print("我是初始纯天然,不加修饰的")
这种效果等同于:
def ordinary():
print("我是初始纯天然,不加修饰的")
ordinary = make_pretty(ordinary)
带参数的装饰器
上面的装饰器很简单,它只适用于没有任何参数的函数。如果对于带参数的函数呢?比如:
def divide(a, b):
return a/b
该函数并没有处理当b
等于0这种情况。那么我们可以通过装饰器将b
等于0这种情况加进去。
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):
print(a/b)
对于装饰器包装过的函数,当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
是位置参数的元组,而kwargs
是关键字参数的字典。这样的decorator示例如下:
def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwargs)
return inner
多个装饰器
在Python中可以链接多个装饰器。也就是说,可以用不同(或相同)的装饰器多次装饰函数。我们只需将装饰器置于所需函数之上。示例如下:
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 World")
运行输出结果:
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello World
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
上述语法:
@star
@percent
def printer(msg):
print(msg)
等同于:
def printer(msg):
print(msg)
printer = star(percent(printer))
装饰器链接的顺序很重要,如果将上述次序颠倒:
@percent
@star
def printer(msg):
print(msg)
输出结果如下:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello World
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%