人总要抱紧什么才知道自己真的存在,哪怕那只是幻影。

最后更新时间:

15:1722
星期二
2019年2月26日

装饰器:在不改变原有代码的情况下,为被装饰的函数,类,以及对象等添加新的功能,或者帮助打印输出。

Python的执行是从上往下顺序执行,遇到函数的时候不会立刻执行,只有函数被调用的时候才会执行函数的代码。但是写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

  • 封闭:已实现的功能代码块
  • 开放:对扩展开发

意思就是,已经写好的函数,不要在这个函数内在调用别的函数。优点是不用修改写好的代码,便于更新和维护。

代码实例

def outer(func):
# 定义装饰器,接受一个参数,这个参数可以是任何对象(装饰器接受一个函数)
    def inner():
        print("装饰器加载开始")
        result = func()
        # 把传入的函数执行,执行后返回的结果赋值给result
        print("装饰器加载成功!")
        # 传入的函数执行后,显示装饰成功
        return result
        # 返回传入函数执行的结果
    return inner
    # 这里返回内部函数本体,这里返回的inner会被赋值给下面的f1,于是f1就变成了inner,f1函数被inner函数覆盖

@outer
# 当程序执行到这里,发现了@outer,立即执行这个装饰器,于是把被装饰对象(就是下面这个函数名:注意是f1并不是f1()传递进去)
def f1():
    # 当上面的执行后,这里f1就是inner。在这里调用f1其实就是调用inner()
    print("f1函数执行......")

f1()

运行结果:

装饰器加载开始
f1函数执行......
装饰器加载成功!

代码剖析

首先不管outer()函数,按照python正常的执行顺序从上往下执行,先执行的是@outer,这里是一个语法糖,Python立刻执行这个函数。然后就是代码中注释的一样,下面的函数作为被装饰对象传参进去,然后outer函数进一步执行,执行到inner函数的时候,先不执行inner函数内部的代码,因为这是一个函数,而是执行return inner,return inner就只返回这个函数本身,于是执行inner内部的代码块,执行result=fun()的作用就是把传入的函数执行,执行后返回的结果赋值给result这个值,然后继续执行,inner函数返回result。在进一步执行,return inner,注意着了并不是return inner(),这里就是返回内部函数的本体,并不是执行inner函数哦。

因为一开始f1函数就被传参进去了,所以执行到return inner的时候,其实就是return f1()的意思,也就是说在这里f1函数被inner函数覆盖了。但是到这里函数并没有被执行,因为这还是一个函数,然后继续执行到f1(),执行f1()就是执行inner()函数,刚刚说到inner函数覆盖了f1函数。

于是执行inner函数。

functools.wraps

python自带的functlls模块可以轻易的实现装饰器功能,Python装饰器(decorator)在实现的时候,有一些细节需要被注意。例如,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。这样有时候会对程序造成一些不便,例如笔者想对unittest框架中的一些函数添加自定义的decorator,添加后由于函数名和函数的doc发生了改变,对测试结果有一些影响。

所以,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。

from functools import wraps

def outer(func):
    @wraps(func)
    def inner(*args,**kwargs):
        print('装饰器执行开始')
        result = func(*args,**kwargs)
        print('装饰器执行完毕')
        return result
    return inner

@outer
def run():
    print('gogogo')

run()

返回结果:

装饰器执行开始
gogogo
装饰器执行完毕

总结

文字模式

语言描述比较抽象,我总结一下装饰器的使用方法。

  1. 首先你有一个函数是之前写好了的,然后你发现业务变动,需要先判断某些环境或者打印一些内容或者满足某些条件才能执行你写好的函数。
  2. 要求不能对你写好的函数修改代码。
  3. 不能在你写好的函数中再调用另一个函数做判断打印功能。
  4. 定义一个装饰器函数,以闭包的形式(不懂闭包请百度)。装饰器函数接受一个参数,然后在装饰器函数内部定义一个函数。
  5. 装饰器内部的函数中,可以写判断打印功能,这些功能执行完毕后,在把传入装饰器的函数执行,返回结果赋值给一个变量,然后可以继续写打印功能之类的代码。
  6. 在装饰器的内部函数的最后,返回之前得到结果的赋值的变量,最后在装饰器函数中返回内部函数本体(不加括号)。
  7. 到这里其实被装饰的函数以及被装饰器的内部函数覆盖了,执行被装饰的函数的时候其实就是在执行装饰器内部的函数。
  8. 最终在不破坏原有代码的情况下,添加新的功能,便于阅读修改维护。

代码模式

def outer(func):
# 定义装饰器,接受被装饰函数作为参数
    def inner():
        # 这个就是装饰器内部的函数了
        print("装饰器加载开始")
        # 这里先写一些执行判断打印等功能
        result = func()
        # 把传入的函数执行,执行后返回的结果赋值给result
        print("装饰器加载成功!")
        # 这里也可以写一些执行判断等功能
        return result
        # 返回传入函数执行的结果
    return inner
    # 这里返回内部函数本体,这里返回的inner会被赋值给下面的f1,于是f1就变成了inner,f1函数被inner函数覆盖

@outer
def f1():
    print("f1函数执行......")
# 一直到这里,整体都是没有执行的。
f1()
# 最终程序执行,f1函数其实已经变成了inner函数,执行f1函数相当于执行inner函数。

inner核心部分就是执行一些代码,然后result=fun()这一步很重要,因为这里就相当于把传入的函数执行,一般的装饰器代码格式都是这样的。

为什么装饰器内还要有一个函数呢?

答:因为Python代码执行是从上往下执行的,当执行到@outer的时候,因为装饰器的特性,这里outer函数立刻执行,所以没有执行f1的时候装饰器就执行了,不符合想要的条件。

如果f1函数需要传递参数怎么办呢?

答:在定义f1函数的时候,f1()改成f1(name),装饰器内部inner函数中inner()改成inner(username),result=fun()改成result=fun(username),最后的执行f1()改成f1(要传入的参数)。
如果要传入多个参数的话,只需要全改成(name,*args)即可。