Python中函数装饰器@

##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的内容。

尽量还是不要使用嵌套的函数修饰符。