装饰器-打扮函数
一、装饰器 意义在于 高阶函数 传进去一个函数作为参数,然后在装饰器中对这个传进来的函数进行加工打扮之后返回一个函数,返回的函数已经不是原来的函数了
带参数的装饰器
上面的例子,我们增强了函数 hello
的功能,给它的返回加上了标签 <i>...</i>
,现在,我们想改用标签 <b>...</b>
或 <p>...</p>
。是不是要像前面一样,再定义一个类似 makeitalic
的装饰器呢?其实,我们可以定义一个函数,将标签作为参数,返回一个装饰器,比如:
def wrap_in_tag(tag): def decorator(func): def wrapped(*args, **kwargs): ret = func(*args, **kwargs) return '<' + tag + '>' + ret + '</' + tag + '>' return wrapped return decorator
现在,我们可以根据需要生成想要的装饰器了:
makebold = wrap_in_tag('b') # 根据 'b' 返回 makebold 生成器 @makebold def hello(name): return 'hello %s' % name >>> hello('world') '<b>hello world</b>'
上面的形式也可以写得更加简洁:
@wrap_in_tag('b') def hello(name): return 'hello %s' % name
这就是带参数的装饰器,其实就是在装饰器外面多了一层包装,根据不同的参数返回不同的装饰器。
多个装饰器
现在,让我们来看看多个装饰器的例子,为了简单起见,下面的例子就不使用带参数的装饰器。
def makebold(func): def wrapped(): return '<b>' + func() + '</b>' return wrapped def makeitalic(func): def wrapped(): return '<i>' + func() + '</i>' return wrapped @makebold @makeitalic def hello(): return 'hello world'
上面定义了两个装饰器,对 hello
进行装饰,上面的最后几行代码相当于:
def hello(): return 'hello world' hello = makebold(makeitalic(hello)) 调用函数 hello: >>> hello() '<b><i>hello world</i></b>'
基于类的装饰器
前面的装饰器都是一个函数,其实也可以基于类定义装饰器,看下面的例子:
class Bold(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return '<b>' + self.func(*args, **kwargs) + '</b>' @Bold def hello(name): return 'hello %s' % name >>> hello('world') '<b>hello world</b>'
可以看到,类 Bold
有两个方法:
__init__()
:它接收一个函数作为参数,也就是被装饰的函数__call__()
:让类对象可调用,就像函数调用一样,在调用被装饰函数时被调用
还可以让类装饰器带参数:
class Tag(object): def __init__(self, tag): self.tag = tag def __call__(self, func): def wrapped(*args, **kwargs): return "<{tag}>{res}</{tag}>".format( res=func(*args, **kwargs), tag=self.tag ) return wrapped @Tag('b') def hello(name): return 'hello %s' % name
需要注意的是,如果类装饰器有参数,则 __init__
接收参数,而 __call__
接收 func
。
装饰器的副作用
前面提到,使用装饰器有一个瑕疵,就是被装饰的函数,它的函数名称已经不是原来的名称了,回到最开始的例子:
def makeitalic(func): def wrapped(): return "<i>" + func() + "</i>" return wrapped @makeitalic def hello(): return 'hello world'
函数 hello
被 makeitalic
装饰后,它的函数名称已经改变了:
>>> hello.__name__ 'wrapped'
为了消除这样的副作用,Python 中的 functools 包提供了一个 wraps 的装饰器:
from functools import wraps def makeitalic(func): @wraps(func) # 加上 wraps 装饰器 def wrapped(): return "<i>" + func() + "</i>" return wrapped @makeitalic def hello(): return 'hello world' >>> hello.__name__ 'hello'