- 目录
- 装饰器
Python装饰器,英文decorator
, 以被装饰函数作为参数对其装饰。这里会用到函数,我们可以像使用变量一样使用函数,函数有对象一致特性。
- 函数可以被赋值给其它变量
- 函数可以内部再定义函数
- 函数可以作为参数传递给另外一个函数
- 函数可以直接返回给外层函数
简单函数进行装饰
有这么一个函数1
2def hello():
return 'hello world'
我们想在不修改原函数的情况下,让函数返回特定标签1
2
3
4def maketag(func):
def wrapper():
return "<h>" + func() + "</h>"
return wrapper
我们定义了一个函数maketag(),该函数有一个参数func,这个参数必须是一个函数,内部又定义了一个wrapper()函数,并直接返回给maketag()1
2
3>>> hello = maketag(hello) # 将 hello 函数传给 maketag
>>> hello()
'<h>hello world</h>'
上面我们把最初hello函数传给maketag()函数并将返回赋值给变量hello,此时调用hello得到我们想要的结果。注意:
maketag()返回赋值给hello,hello函数本身还存在,但函数名引用己经变为了maketag返回函数的名称wrapper,不在是原来的hello,验证一下1
2
3
4
5
6
7In [6]: a = maketag(hello)
In [8]: a.__name__
Out[8]: 'wrapper'
In [9]: hello = maketag(hello)
In [10]: hello.__name__
Out[10]: 'wrapper'
总结一下:为了增强原函数hello的功能定义了一个maketag函数,它接收一个函数作为参数,maketag返回一个新的函数赋值给一个变量hello,然后hello可直接调用。
一般情况下,我们使用装饰器提供的 @ 语法糖(Syntactic Sugar)来简化上面的写法:1
2
3
4
5
6
7
8def maketag(func):
def wrapper():
return "<h>" + func() + "</h>"
return wrapper
@maketag
def hello():
return 'hello world'
像上面的情况,可以动态修改函数(或类)功能的函数就是装饰器。本质上,它是一个高阶函数,以被装饰的函数(比如上面的 hello)为参数,并返回一个包装后的函数(比如上面的 wrapper)给被装饰函数(hello)。
单/多个装饰器的使用形式
装饰器的一般使用形式如下:1
2
3
4
5
6
7
8@decorator
def func():
pass
等价于下面的形式:
def func():
pass
func = decorator(func)
装饰器可以定义多个,离函数定义最近的装饰器先被调用,比如:1
2
3
4
5
6
7
8
9
10@decorator_one
@decorator_two
def func():
pass
等价于:
def func():
pass
func = decorator_one(decorator_two(func))
看下多个装饰器的例子,为了简单起见,下面的例子就不使用带参数的装饰器。1
2
3
4
5
6
7
8
9
10
11
12
13
14def maketag1(func):
def wrapper():
return '<html>' + func() + '</html>'
return wrapper
def maketag2(func):
def wrapper():
return '<i>' + func() + '</i>'
return wrapper
@maketag1
@maketag2
def hello():
return 'hello world'
上面定义了两个装饰器,对 hello 进行装饰,上面的最后几行代码相当于:1
2
3
4
5
6
7
8def hello():
return 'hello world'
hello = maketag1(maketag2(hello))
调用函数 hello:
>>> hello()
'<html><i>hello world</i></html>'
似乎理解深刻了:当多个装饰器时,离函数定义最近的装饰器先被调用
对带参数的函数进行装饰
让被装饰函数带有参数,对前面例子中的 hello() 函数进行改写使其带参数,如下:
1 | def maketag(func): |
由于函数 hello 带参数,因此内嵌包装函数 wrapper 也做了一点改变:
内嵌包装函数的参数传给了 func,即被装饰函数,也就是说内嵌包装函数的参数跟被装饰函数的参数对应,这里使用了 (*args, **kwargs),是为了适应可变参数。
看看使用:1
2
3
4>>> hello1('python')
'<h>hello python</h>'
>>> hello2('python', 'java')
'<h>hello python, java</h>'
带参数的装饰器
装饰器还可以带参数,比如:1
2
3
4
5
6
7
8
9@decorator(args1, args2)
def func():
pass
等价于:
def func():
pass
func = decorator(args1, args2)(func)
我们想改用标签 <html>...</html>
是不是要再定义一个装饰器呢?不必,其实我们可以装饰器外层再定义一个函数,将标签作为参数,返回一个装饰器,比如:1
2
3
4
5
6
7
8def outer_decorator(tag): # 外层函数参数传给装饰器
def decorator(func): # 真正的装饰器参数必然是被装饰器函数func
def wrapper(*args, **kwargs): # 装饰器内层函数的参数是func的参数
res = func(*args, **kwargs) # 被装饰函数正常调用
# return '<' + tag + '>' + res + '</' + tag + '>'
return '<{tag}>{res}</{tag}>'.format(tag=tag, res=res)
return wrapper # 内层函数作返回值返回给装饰器
return decorator # 装饰器本身作为返回值返回给最外层函数
现在,我们可以根据需要生成想要的装饰器了:1
2
3
4
5
6
7outer_decorator = outer_decorator('html')
@outer_decorator
def hello(name):
return 'hello %s' % name
>>> hello('world')
'<html>hello world</html>'
上面的形式也可以写得更加简洁:1
2
3@outer_decorator('html') # @语法糖syntactic sugar
def hello(name):
return 'hello, %s' % name
这就是带参数的装饰器,其实就是在装饰器外面多了一层包装,根据不同的参数返回不同的装饰器。
基于类的装饰器
前面的装饰器都是一个函数,其实也可以基于类定义装饰器,看下面的例子:1
2
3
4
5
6
7
8
9
10
11
12
13class Deco_C(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return '<html>' + self.func(*args, **kwargs) + '</html>'
@Deco_C
def hello(name):
return 'hello %s' % name
>>> hello('world')
'<html>hello world</html>'
可以看到,类 Deco_C 有两个方法:
__init__():
它接收一个函数作为参数,也就是被装饰的函数__call__():
让类对象可调用,就像函数调用一样,在调用被装饰函数时被调用
还可以让类装饰器带参数:
1 | class Tag(object): |
需要注意的是,如果类装饰器有参数,则 init 接收此参数,而 call 接收 func函数,并多了一层wrapper(*args, **kwargs)来接收func函数的参数。
装饰器的副作用
前面提到装饰器有一个瑕疵,就是被装饰函数的函数名称已经不是原来的名称了,回到最开始的例子:1
2
3
4
5
6
7
8def maketagc(func):
def wrapper():
return "<i>" + func() + "</i>"
return wrapper
@maketag
def hello():
return 'hello world'
函数 hello 被 maketag 装饰后,它的函数名称已经改变了:1
2>>> hello.__name__
'wrapper'
为了消除这样的副作用,Python 中的 functool 包提供了一个 wraps 的装饰器:1
2
3
4
5
6
7
8
9
10
11
12
13
14from functools import wraps
def maketag(func):
@wraps(func) # 加上 wraps 装饰器
def wrapper():
return "<i>" + func() + "</i>"
return wrapper
@maketag
def hello():
return 'hello world'
>>> hello.__name__
'hello'
小结:
本质上,装饰器就是一个有返回函数的高阶函数。
装饰器可以动态地修改一个类或函数的功能,通过在原有的类或者函数上包裹一层修饰类或修饰函数实现。
事实上,装饰器就是闭包的一种应用,但它比较特别,以被装饰函数为参数,并返回一个函数,赋给被装饰函数,闭包则没这种限制。
装饰器生产化进阶
eg1:1
2
3
4
5
6
7
8
9
10
11
12
13
14def deco(func):
def wrapper(x):
print("what ...")
func(x)
print('Game over ...')
return wrapper
@deco
def show(x):
print(x)
>>>show("I am a pythoner")
what ...
I am a pythoner
Game over ...
eg2: