1、什么是装饰器
Python中的装饰器decorator是一个非常有用的功能。
相信学过设计模式的同学都会或多或少的了解一点,简单来说装饰器decorator能够在无需修改原有函数的情况下动态地修正一个函数,类或者类的方法的功能。即当你希望在不修改函数本身的前提下扩展函数的功能时非常有用。
形象的说,装饰器就像一个外包装wrapper,在函数执行之前或者之后调用,用来修改原有函数的行为,但是不需修改函数本身的代码,这也是修饰器名称的来由。
2、Python函数
我们在开始学习Python的时候就知道这个说法:在Python中,函数本身也是对象,这是什么意思呢?就是说我们可以像操作类对象一样操作函数。下面我们通过一些演示说明一下。
2.1 将函数赋值给变量:
def func_test(name):
return "hello " + name
test = func_test # 将函数赋值给test变量,类似于重命名
print greet_someone("World") # Outputs: hello World
2.2 函数内嵌套函数:
def func_out(name):
def func_in():
return "Hello "
result = func_in() + name
return result
print func_out("World") # Outputs: Hello World
2.3 函数被当做参数传给其他函数:
def func_in(name):
return "Hello " + name
def func_out(func):
other_name = "John"
return func(other_name)
print func_out(func_in) # Outputs: Hello World
2.4 函数可以返回其他函数(函数产生函数):
def func_out():
def func_in():
return"Hello World!"
return func_in
test = func_out()
print test() # Outputs: Hello World!
2.5 内部函数可以访问外层函数变量
def func_out(name):
def func_in():
return"Hello " + name
return func_in
test = func_out("World")
print test() # Outputs: Hello World
这里的内嵌函数使用了外部函数的参数,上面这种函数,便被称为闭包。跟普通函数的不同是,内嵌函数func_in用了外部函数的变量name,使外部变量生命周期被延长了。而普通函数的变量,在进入其他函数时,变量便无法再用了
这种内部函数调用外部变量的行为,就叫做闭包。
3、装饰器
3.1 装饰器实现
说了半天,闭包和装饰器有什么关系呢?
我们平常用的装饰器,就是闭包实现的
这里我们传入都是一个字符串作为参数,如果传入函数作为参数呢,这就是我们要说的装饰器了。
到底如何实现呢?话不多说,直接上代码。
def ori_func(text):
return"{0}".format(text)
def func_decorate(func):
def func_a(name):
return"<a>{0}</a>".format(func(name))
return func_a
my_func = func_decorate(ori_func)
print my_func("hello World ") # <a>hello World </a>
这就是我们实现的修饰器。一个函数接收另一个函数作为参数,并且产生一个新的函数,在原有函数ori_func功能基础上添加新功能,并且返回一个新函数func_a 。然后我们将修饰器函数func_decorate直接赋值给my_func,这样就有了自己想要的函数
my_func = func_decorate(ori_func)
需要注意的是:被修饰的函数ori_func具有一个text参数,我们必须在赋值操作时传入那个参数。
那如果我不想改变函数名称呢,这个时候只要在赋值语句时,使用原有函数名就可以了,这样就覆盖了原来的函数!
def ori_func(text):
return"{0}".format(text)
def func_decorate(func):
def func_a(name):
return"<a>{0}</a>".format(func(name))
return func_a
ori_func = func_decorate(ori_func) # 覆盖了原来的函数
print ori_func("hello World ") # <a> hello World </a>
3.2 修饰符语法
在上面的例子中我们通过ori_func = func_decorate(ori_func)的方式覆盖了原有函数,从而形成了有新功能的同名函数,这个显得有点啰嗦,python提供了简洁清晰的对应语法。比如:
def func_decorate(func):
def func_a(name):
return"<a>{0}</a>".format(func(name))
return func_a
@func_decorate
def ori_func(text):
return"{0}".format(text)
print ori_func("hello World ") # <a>hello World </a>
上述代码中,@符号后面的是修饰器本身,紧跟后面的则是将被修饰的函数(这种语法等价于使用@后面的修饰器先对ori_func修饰,并且返回产生的新函数替代被修饰的函数名。
相当于用@func_decorate 替换了ori_func = func_decorate(ori_func)
那就有同学又问了,添加多个修饰符可不可以呢?当然可以,不过修饰符的顺序不同,效果也不同
def func_decorate_a(func):
def func_a(name):
return"<a>{0}</a>".format(func(name))
return func_a
def func_decorate_div(func):
def func_a(name):
return"<div>{0}</div>".format(func(name))
return func_a
def func_decorate_td(func):
def func_a(name):
return"<td>{0}</td>".format(func(name))
return func_a
@func_decorate_div
@func_decorate_td
@func_decorate_a
def ori_func(text):
return"{0}".format(text)
print ori_func("hello World ")
#output: <div><td><a> hello World </a></td></div>
从结果可以看出,装饰器是逐级向下装饰的效果,也就是从下往上执行的顺序。
3.3 类方法的装饰器实现
def func_decorate(func):
def func_a(name):
return"<a>{0}</a>".format(func(name))
return func_a
class Test(object):
def __init__(self):
self.name = "test_name"
@func_decorate
def get_name(self):
return self.name
obj = Test()
print my_person.get_name()
一定需要注意的是,由于类方法中的第一个变量是self ,虽然最后调用时my_person.get_name()没带参数,但是装饰器函数中的func依然要带参数,此处是 return"{0}".format(func(name)) 语句中的name
这时候我们就想了,有些时候我们希望一个装饰器 可以修饰不通的函数,但是被修饰函数参数又不尽相同,这怎么办呢?
经常用Python的同学肯定脑海中第一时间想到了,当然是args和*kwargs 了
def func_decorate(func):
def func_a(*args, **kwargs):
return"<a>{0}</a>".format(func(*args, **kwargs))
return func_a
class Test(object):
def __init__(self):
self.name = "test_name"
@func_decorate
def get_name(self):
return self.name
obj = Test()
print my_person.get_name()
这样可以接受任意个数的参数或者keyword型参数。
3.4 装饰器传入参数
上面的各例子中装饰器都只包含唯一的参数,就是传入的被修饰函数
如果想要传入其他参数呢,例如上述2.2中多个装饰器,明显代码重复冗余,如何通过传入参数优化实现呢,请看下面代码:
def add_tag(tag):
def func_decorate(func):
def func_a(*args, **kwargs):
return"<{1}>{0}</{1}>".format(func(*args, **kwargs),tag)
return func_a
return func_decorate
@add_tag("td")
@add_tag("div")
def ori_func(text):
return"{0}".format(text)
print ori_func("hello World ") # <div>hello World </div>
这样是不是就灵活多了。
3.5 装饰器代码调试
看代码时经常会看到@wraps修饰符 出现在装饰器中,这到底是干什么用的呢?下面我们就来说明一下。
通过以上学习我们知道,装饰器返回的函数func_a重载了原有函数get_name,这带来一个问题就是如果要调试代码可能有问题,因为装饰器函数并不会携带原函数的函数名、模块名和docstring等信息,
例如上面的例子,如果我们打印print(ori_func.name)则返回func_a
而不是ori_func,原因就是__name__,doc,__module__这些属性都被func_a函数所重载。虽然我们可以手工重置(在func_a里面),但是python提供了更好的办法:
functools
functools模块包含了wraps函数。wraps也是一个decorator,但是仅仅用于更新装饰器(func_a)的属性为原始函数的属性(ori_func),看下面的代码:
from functools import wraps
def add_tag(tag):
def func_decorate(func):
@wraps(func)
def func_a(*args, **kwargs):
return"<{1}>{0}</{1}>".format(func(*args, **kwargs),tag)
return func_a
return func_decorate
@add_tag("td")
@add_tag("div")
def ori_func(text):
return"{0}".format(text)
print ori_func("hello World ") # <div>hello World </div>
print(ori_func.__name__)
这样再运行 print(ori_func.name) 则输出 ori_func 了