Bootstrap

Python 装饰器

一、什么事装饰器:

装饰器的本质是一个函数,在不改动其他函数的前提下,对函数的功能进行拓展,举一个知乎上引用的例子,冬天天冷了不能只穿短裤了,要在短裤之外套上长裤,但是长裤并没有影响短裤遮羞的最根本作用。个人觉得有点类似java里面的aop

二、装饰器的常用场景:

  1. 引入日志
  2. 统计函数执行时间
  3. 执行函数前预处理
  4. 执行函数后的清理功能
  5. 权限校验
  6. 制作缓存
  7. 使用装饰器扩展类的功能

三、装饰器的类型

3.1.最简单的装饰器:

def decorate(func):
    print('正在装饰')
    def decorate_inner():
        print('正在验证')
        func()
    return decorate_inner

@decorate
def func_test():
    print('func_test is running')

func_test()

分析一下上述被修饰函数func_test的执行流程:

  1. 执行func_test()的时候,发现func_test头顶有装饰器decorate,所以会率先执行decorate。@decorate等价于func_test = decorate(func_test);
  2. 首先执行decorate(func_test),以函数func_test作为参数,在调用decorate的时候,首先会执行print('正在装饰')语句,然后返回其内函数decorate_inner。
  3. 从而函数func_test 接受了 装饰器的 内函数 decorate_inner,即func_test=decorate_inner。当开始调用func_test()的时候,即相当于调用了decorate_inner(),从而执行decorate_inner的调用,执行print('正在验证'),再执行func_test本体。

3.2.多个装饰器:

def decorate1(func):
    print('正在装饰————1')
    def decorate_inner():
        print('正在验证————1')
        func()
    return decorate_inner

def decorate2(func):
    print('正在装饰————2')
    def decorate_inner():
        print('正在验证————2')
        func()
    return decorate_inner

@decorate1
@decorate2
def func_test():
    print('func_test is running')

func_test()

分析一下上述被修饰函数func_test的执行流程:

  1. 当执行函数func_test()的时候,发现该函数具有两个装饰器,装饰器执行的顺序从下至上。
  2. 先执行第2个装饰器decorate2,把func_test作为参数传给decorate2,相当于func_test=decorate2(func_test)
  3. 然后在执行第1个装饰器decorate1,把decorate2的返回值作为参数传给decorate1,相当于func_test=decorate1(decorate2(func_test))
  4. 所以当执行func_test的时候控制台的展示应该是:
    正在装饰————2
    正在装饰————1
    正在验证————1
    正在验证————2
    func_test is running
  5. 当装饰器更多的时候,依此顺序累加。

3.3.有参数函数的装饰器

如果被修饰函数带有参数是,继续使用3.1的装饰器则会报错,需要将装饰器的写法进行如下修改

def decorate(func):
    print('正在装饰')
    def decorate_inner(*args, **kwargs):
        print('正在验证')
        func(*args, **kwargs)
    return decorate_inner

@decorate
def func_test(param):
    print('func_test is running',str(param))

func_test('sth param')

分析使用3.1那种方法会报错的原因、上述被修饰函数func_test的执行流程:

  1. 当func_test被decorate修饰的时候,func_test的实际指向就变成了decorate函数的inner函数,但是3.1的inner函数并没有接受参数,所以用3.1的装饰器去修饰有参数的函数时就会抛出异常。
  2. 本文所介绍的装饰函数 的 inner 函数在接受参数的时候使用了,*args 和 **kwargs 这样做的好处是,当被修饰函数的参数未知的情况下,修饰函数可以对参数进行动态的匹配

3.4.修饰有返回值的函数

如果修饰函数存在返回值,那么如果继续使用上述3.1到3.3的装饰器,则会使print 出的值总是None

def decorate(func):
    print('正在装饰')
    def decorate_inner(*args,**kwargs):
        print('正在验证')
        return func(*args,**kwargs)
    return decorate_inner

@decorate
def func_test():
    print('func_test is running')
    return 'return sth'

print func_test()

分析原因及上述被修饰函数func_test的执行流程:

  1. 因为func_test被decorate修饰,所以func_test的指引变成了decorate的内函数decorate_inner,但是3.1到3.3的decorate_inner都不具备返回值,所以print出来的值永远是None
  2. 所以,这个时候就要将decorate_inner中的func()作为返回值return出来。

3.5.带有参数的装饰器

def decorate(*args, **kwargs):
     print 'decorate is running', args, kwargs
    def decorate_outer(func):
        print 'decorate_outer is running'
        def decorate_inner(*args, **kwargs):
            print 'decorate_inner is running'
            return func(*args, **kwargs)
        return decorate_inner
    return decorate_outer


@decorate('deco')
def func_test():
    print 'func_test is running'
    return 'sth return'

print func_test()

上述函数的运行结果如下:

decorate is running ('deco',) {}
decorate_outer is running
decorate_inner is running
func_test is running
sth return

相比于无参数装饰器,带参数的装饰器只是用来‘加强装饰’的,如果希望装饰器可以根据参数的不同,对不同的函数进行不同的装饰,那么带参装饰器是一个很好的选择。

函数运行结果社上述大同小异,此后不做多余解释。

3.6.类装饰器

class Decorate():
    
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print 'class is decorating'
        self._func()
        print 'decorate is ending'


@Decorate
def func_test():
    print 'func_test is running'

func_test()

四、拓展——关于wraps和拓展类的功能

4.1.wraps

使用装饰器虽然使代码得以复用,但是却覆盖了原函数的一些信息,比如__name__等。

具体情况如下:

def decorate(func):
    print('正在装饰')
    def decorate_inner():
        print('正在验证')
        func()
    return decorate_inner

@decorate
def func_test():
    print('func_test is running')

print func_test.__name__

这个时候得到的值为:

decorate_inner

但是我们的函数名称为func_test,所以这个时候需要引入Python基本库中的functools。

具体实现如下:

from functools import wraps

def decorate(func):
    print('正在装饰')
    @wraps(func)
    def decorate_inner():
        print('正在验证')
        func()
    return decorate_inner

@decorate
def func_test():
    print('func_test is running')

print func_test.__name__

这样控制台打印出的就是我们预期的效果

func_test

4.2.使用装饰器拓展类的功能

因为篇幅已经有点长了,本文就不详细介绍了,感兴趣的童鞋可以挪步下面的link

https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p12_using_decorators_to_patch_class_definitions.html

 

PS:本文参考了《Python快速编程入门》一书和知乎《如何理解Python装饰器》的部分内容

 

;