Python装饰器

装饰器模式是一个强大的模式,可以给一个函数增加额外的方法而不用修改原来的代码。比如,当我们需要给原来的代码添加日志记录时,需要将原来的代码进行修改,在函数里面增加日志记录的代码。但如果使用装饰器模式,使用装饰器装饰原来的代码,将日志记录放在装饰器里,就可以避免修改原有代码,同时又实现了所需的功能。这种编程模式就叫做面向切面编程。

日志记录实例

比如,我们有一个函数func:

def func():
    print('func')

现在,我们需要记录一下这个函数执行时的日志记录,即记录一下函数执行的时间,输出执行时间和方法名。日志输出函数如下:

from datetime import datetime as dt
def log(func):
    print('['+str(dt.now())+']'+func.__name__)
    func()

好了,当我们想使用func函数的时候该怎么做呢?不是直接func(),而是如下:

log(func)

那么,会输出:

[2017-09-10 20:51:09.970854]func

这就实现了调用func时,同时进行日志记录。但这样做很明显会有问题,就是我们需要把所有调用func的地方改为log(func),这很麻烦,并且会需要改很多代码。所以,我们换一种写法:

def log(func):
    def wrapper():
        print('['+str(dt.now())+']'+func.__name__)
        func()
    return wrapper

请注意,这就是一个装饰器了!

里面那个wrapper的意思就是装饰、包裹的意思。我们将func函数装饰一下变成了一个新的函数然后返回它。那该如何使用呢?如下:

func = log(func)
func()

使用方法就是用log装饰器将func装饰一下后返回覆盖掉原有的func即可。

装饰器语法糖

所谓的语法糖的意思就是添加一个语法,让代码更简单的意思。Python为装饰器也提供了一个语法糖,就是@符号。我们可以使用这个符号声明这是一个装饰器。如下,我们使用语法糖的写法给func添加log这个装饰器:

@log
def func():
    return print('func')

这样写好后,直接调用func()即可同时输出日志了:

[2017-09-10 21:10:50.933021]func
func

装饰器给函数传参

我们把func函数改一改:

def func(msg):
    print("-->" + msg)

现在,我们要给函数输入参数了,而原来那个log装饰器是没有带参数的,所以用不了了。下面,我们就要实现一个可以给函数传参数的装饰器:

def log(func):
    def wrapper(msg):
        print('['+str(dt.now())+']'+func.__name__)
        return func(msg)
    return wrapper

这样一改,log就又可以用了,给func再加上语法糖:

@log
def func(msg):
    print("-->" + msg)

# output
func('hello')

output:

[2017-09-10 21:16:26.677027]func
-->hello

这样一改,装饰器就又可以用了。那如果我再给func又加一个参数呢?再加一个参数呢?我如果给func加不定数个参数呢?又该怎么做呢?再改log吗?我知道大家心里肯定也意识到了这个方法的不靠谱。

别着急,下面我们就写一个更强力的log装饰器,来让装饰器可以装饰任何函数,而不用管那个函数又多少个参数。使用Python的可变参数*args和关键字参数**kwargs即可。修改log,如下:

def log(func):
    def wrapper(*args, **kwargs):
        print('['+str(dt.now())+']'+func.__name__)
        return func(*args, **kwargs)
    return wrapper

带参数的装饰器

装饰器本身也可以带参数,比如说,我们给log装饰器添加一个参数is_show用于是否显示日志输出。如下:

def log(is_show=True):
    def wrapper(func):
        def inner_wrapper(*args, **kwargs):
            if is_show:
                print('['+str(dt.now())+']'+func.__name__)
            return func(*args, **kwargs)
        return inner_wrapper
    return wrapper

作为对比,我们写两个func

# 默认显示日志
@log(True)
def func1():
    print("func1")

# 默认不显示日志
@log(False)
def func2():
    print("func2")