python中的@

@在python中是函数的修饰符,为函数提供了包装的功能,为函数提供了更细粒度的扩展控制。详见官方文档

本文仅从使用的角度来探究一下@的特性。

例1

1
2
3
4
5
6
7
8
9
def wrapper(fn):
print("This is a wrapper!")

@wrapper
def func():
print("func")

if __name__ == "__main__":
func()

输出为:

1
2
3
4
5
This is a wrapper!
Traceback (most recent call last):
File "***.py", line 9, in <module>
func()
TypeError: 'NoneType' object is not callable

在执行line 9前这里并没有任何函数调用,但是wrapper已经被执行了。这是因为在解析到@wrapper的时候,等效的代码为:

1
func = wrapper(func)

这也就解释了为什么调用func会报NoneType的错误(wrapper没有return value,因此funcNone)。wrapper的参数fn实际上就是func,在wrapper末尾加上return fn,程序的输出为:

1
2
This is a wrapper!
func

例2

1
2
3
4
5
6
7
8
9
10
11
12
def wrapper(base):
print("base = {}".format(base))
def inner(fn):
return fn
return inner

@wrapper(8)
def func(num):
print("num = {}".format(num))

if __name__ == "__main__":
func(7)

输出为:

1
2
base = 8
num = 7

wrapper也可以有参数,这里接收一个参数,func就不能像例1那样直接传过来,而是要在wrapper里面新定义一个函数(名称随意),这个内部函数将会得到被包装的函数。

进一步地想,在wrapper里面既然获得了原函数,我们能否获得原函数的参数?

例3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def wrapper(base):
print("base = {}".format(base))
def inner(fn):
def new_func(num):
print("result = {}".format(fn(num) * base))
return new_func
return inner

@wrapper(8)
def func(num):
return num + 2

if __name__ == "__main__":
func(7)

输出为:

1
2
base = 8
result = 72

inner里面再定义一个内部函数,这个函数就可以获得原函数的参数。在这个例子中,我们将这个内部函数返回,而且这个内部函数还调用了原函数。

在真实的场景中,往往还会用到fn.__name__来获取被包装的函数的名称来定制化包装。

这里就到尽头了,在new_func中再定义函数就不会得到任何被包装函数的信息,就和普通的嵌套函数类似了。

例4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def wrapper(base):
print("base = {}".format(base))
def inner(fn):
print(fn.__name__)
def new_func(num):
print("result = {}".format(fn(num) * base))
return new_func
return inner

def wrapper2(fn):
print("This is wrapper2.")
fn(3)

@wrapper2
@wrapper(8)
def func(num):
return num + 2

输出为

1
2
3
4
base = 8
func
This is wrapper2.
result = 40

函数包装是可以嵌套的,这里将wrapper包装后的函数传入wrapper2wrapper2如例1所示,接收一个参数,这个参数即是wrapper包装后返回的函数new_funcwrapper2中调用fn,得到了预料中的输出result = 40