「python」装饰器

装饰器介绍和基本使用

Posted by odin on April 9, 2020

装饰器是python的一个特点,首先我们要知道,与javaC++等语言不一样,在python中函数是可以当做参数传递给另一个函数的。如:

1
2
3
4
5
def func_1():
    print ("this is func_1")
    
def func_2(func_1):
    func_1()

明白了这点我们现在开始聊一下python的装饰器。

什么是装饰器

装饰器其实就是一种闭包函数或类,有函数装饰器和类装饰器两种。以函数装饰器来说,它与一般的闭包函数不同的是传递的参数是一个函数方法,并且返回值也是一个函数对象。类装饰器同理,只不过传递参数和返回值是类对象。 下面是一个简单的装饰器

1
2
3
4
5
6
def zsq_1(func):
    def wapper():
        print ("start")
        func()
        print ("done")
    return wapper

在示例中看出,zsq_1方法接收一个函数对象funcwapper方法调用了zsq_1方法的funczsq_1返回wapper对象。

返回值不能带(),否则就是调用执行方法,这里不做详细解释。

装饰器的作用

在上面的示例中@zsq_1装饰器的作用不难看出(@是python提供方便使用装饰器的语法糖方式),在调用执行func方法的开始打印start,执行结束后打印done。装饰器的作用就是在不改变原函数或者类的基础上对原函数增加一些额外的功能。 常用场景例如:日志、事务处理、缓存、权限校验等。 有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

装饰器的缺陷

@zsq_1为例,当我们使用装饰器后,会改变被原函数的元信息。

1
2
3
4
5
6
7
@zaq_1
def func_1():
    """this is func_1 doc string"""
    print ("this is func_1")
    
print (func_1.__name__)     # 打印输出为wapper
print (func_1.__doc__)       # 打印输出为none

在上述方法中,我打印func_1__name__,输出结果为wapper而不是func_1,添加装饰器会重写函数的名字(__name__)和注释文档(__doc__),这个问题是不太能接受的。我们需要一种方法解决这个问题。 好在python给我们提供了functools.wraps方法解决这个问题。wraps本身也是一个装饰器,接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from functools import warps

def zsq_1(func):
    @warps(func)
    def wapper():
        print ("start")
        func()
        print ("done")
    return wapper
    
@zsq_1
def func_1():
    """this is func_1 doc string"""
    print ("this is func_1")
    
print (func_1.__name__)     # 打印输出为func_1
print (func_1.__doc__)       # 打印输出为this is func_1 doc string

带参数的装饰器

装饰器也是可以带有参数的,可以理解成在普通的不带参数的装饰器在包裹一层,下面简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def ZSQ(name="abc"):
    def zsq_1(func):
        @wraps(func)
        def wapper(*args, **kwargs):
            print("start  " + name)
            func()
            print("done  " + name)
            return func
        return wapper
    return zsq_1

@ZSQ(name="aaaaaa")
def func_1():
    print("this is func_1")
    
@ZSQ()
def func_1():
    print("this is func_1")

简单来说,当我们使用日志装饰器时,对不同的方法想要将日志打印到不同的路径时,带参数的装饰器就很好解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from functools import wraps

def logit(logfile='out.log'):
    def logging_decorator(func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            with open(logfile, 'a') as opened_file:     # 打开logfile,并写入内容
                opened_file.write(log_string + '\n')    # 现在将日志打到指定的logfile
            return func(*args, **kwargs)
        return wrapped_function
    return logging_decorator

@logit()
def myfunc1():
    pass

myfunc1()    # Output: myfunc1 was called# 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串

@logit(logfile='func2.log')
def myfunc2():
    pass

myfunc2()    # Output: myfunc2 was called# 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串