在前段时间的面试中,我被问了一个很基础的问题:decorator怎么写。然而很遗憾的是我当时确实就是写错了。我想这确实是对装饰器理解有误造成的。

为什么使用装饰器

使用装饰器的目的是更好地复用代码。使用装饰器可以在原有函数的基础上添加一些功能而不用对函数进行修改。

错误的写法

当被问及怎么写一个解释器的时候,因为没做好要被问Python问题的准备。然后就写出了下面这个错误的版本:(当时是手写的,因此没有办法执行而看到结果)

1
2
3
def wrapper1(func):
print('before func')
return func

装饰器是接受一个函数返回一个函数,我以为它的作用是每次都给包上这个函数,从而达到每次都执行的目的。

但是实际如果在解释器中运行的话,就能看到发生了什么事情:

1
2
3
4
5
6
7
8
9
10
11
12
In [1]: def wrapper1(func):
...: print('before func')
...: return func
...:

In [2]: @wrapper1
...: def tempFunc():
...: print('func')
before func

In [3]: tempFunc()
func

在定义tempFunc的时候,可以看到已经有了一个输出,但是使用这个函数的时候却没有输出。

这是我以为会发生的事情,每一次调用tempFunc都会执行下面的这个操作:

1
wrapper1(tempFunc)()

如果是这样的话,上面那种写法是没有问题的。

但是实际发生的情况是:

1
2
3
tempFunc = wrapper1(tempFunc) # 相当于只执行一次

tempFunc() # 以后的每次执行都是这样

在知道了原理是这样的之后,我们才能将程序写对。

正确的写法

1
2
3
4
5
6
7
def wrapper2(func):
def inner(*args, **kwargs):
print('before func')
res = func(*args, **kwargs)
print('after func')
return res
return inner

这样我们能得到正确的解释器。

1
2
3
4
5
6
7
8
9
In [8]: @wrapper2
...: def tempFunc():
...: print('func')
...:

In [9]: tempFunc()
before func
func
after func

这其中用到了一些python可变参数写法,但是其实并不是特别的麻烦。

带参数的装饰器

装饰器也是可以带参数的。我们还是需要返回一个函数,只是这个函数

1
2
3
4
5
6
7
8
def wrapper3(text):
def logger(func):
def inner(*args, **kwargs):
print('text: ' + text)
print('log func')
return func(*args, **kwargs)
return inner
return logger

实际使用结果:

1
2
3
4
5
6
7
8
9
In [12]: @wrapper3('hello')
...: def tempFunc():
...: print('func')
...:

In [13]: tempFunc()
text: hello
log func
func

其实理解起来也并不难,我们对这个函数做的操作是这样的:

1
tempFunc = ((wrapper3('hello'))(tempFunc))()

所以我们需要做的是,接受一个参数,返回一个普通的装饰器。然后这个装饰器中又是接受一个函数,返回一个函数的。

总结

想起了当时考官戏谑的眼神……