##Python中函数装饰器@
Python中的@是函数修饰器,它其实是种语法糖。本身并不会语言的功能产生影响,但可以更方便程序员的使用。让代码看起来更清晰和简洁。
def funA(fn):
...
def funB():
...
funB = funA(funB)
在上面这段代码中我们需要注意几个地方:
- 这里定义了两个函数,分别是funA和funB。而funA是有参数的。
- 在这段代码的最后还执行了一行语句。函数funB被当作参数传入了funA。并且,把funB重新赋值成了funA(funB)的返回值。
在上面的代码中有一件事悄悄发生了,那就是函数funB的地址已经找不到了。
代码中的函数funB已经成为了funA(funB)的返回值。如果返回值为None,那funB则为None。
如果使用了函数装饰器@,那上面的代码与下面的代码是等价的。
#funA 作为装饰器函数
def funA(fn):
...
#funB 作为被装饰函数,funB 被 funA 装饰
@funA
def funB():
...
这段代码同样有值得注意的地方:
- 函数funA是必须有参数的,因为它需要把函数funB作为参数传入。
- 语句funB = funA(funB)似乎“消失”了。但这条语句依然会被执行,只是被隐藏了。
结合上面两段代码来看,函数装饰器@的作用就是把被装饰函数传给装饰函数,以此来进行功能上的扩展。但需要强调,被装饰函数会被重新赋值为装饰器的返回值!
##单个修饰器,修饰不带参数的函数
函数装饰器@经常可以被用来,插入日志、性能测试、事务处理、记录时间戳等等。
假设现在我们新写了个功能,需要用日志进行测试。但我们不应该直接把日志功能加入到新的函数中。因为这会导致功能耦合,代码的可读性非常的差。在这种情况下,我们可以来看个使用函数装饰器@的例子:
def log(function):
def wrapper():
print("log start...")
function()
print("log end...")
return wrapper
@log
def test():
print("test...")
test()
代码执行结果如下:
log start...
test...
log end...
上面这段代码等价于:
def log(function):
def wrapper():
print("log start...")
function()
print("log end...")
return wrapper
def test():
print("test...")
test = log(test)
test()
因为函数log返回了一个函数给test,实际上也就是返回了函数的地址给test。这也test才能被调用。如果函数log返回None,那test的地址内容也就是None,会抛出错误:
TypeError: ‘NoneType’ object is not callable
因为标识符test为None,而我们却想把空值None当作函数来调用,这当然是不可行的。
##单个修饰器,修饰带参数的函数
上面被修饰的函数是没有传入参数的,如果我们新写的函数是有传入参数的怎么办呢?我们可以这样写:
def log(function):
def wrapper(*arg, **kwargs):
print("log start...")
function(*arg, **kwargs)
print("log end...")
return wrapper
@log
def test(arg):
print(arg)
test("test_num")
代码的执行结果如下:
log start...
test_num
log end...
使用“*arg, **kwargs”的话被装饰函数test的所有参数都会被传入到装饰器中。
##多个修饰器
一个被函数可以被多个修饰器修饰。假设,我某个函数我既要观察日志,也需要记录时间戳。我们就可以使用函数修饰器来进行多个事务上的处理。
def log(function):
def wrapper(*arg, **kwargs):
print("log start...")
function(*arg, **kwargs)
print("log end...")
return wrapper
def time_stamp(function):
def wrapper(*arg, **kwargs):
print("time start...")
function(*arg, **kwargs)
print("time end...")
return wrapper
@time_stamp
@log
def test(arg):
print(arg)
test("test_num")
代码的执行结果如下:
time start...
log start...
test_num
log end...
time end...
在上面的修饰器会先被执行。
还需要考虑一个问题,修饰器可以嵌套么?
def log(function):
def wrapper(*arg, **kwargs):
print("log start...")
function(*arg, **kwargs)
print("log end...")
return wrapper
@log
def time_stamp(function):
def wrapper(*arg, **kwargs):
print("time start...")
function(*arg, **kwargs)
print("time end...")
return wrapper
@time_stamp
def test(arg):
print(arg)
对于上面这段代码,并没有按照预期的那样正常运行,会抛出错误。即使在内置函数wrapper加上返回值return function,也不会按照预期的那样执行函数test的内容。
尽量还是不要使用嵌套的函数修饰符。