Python - 装饰器

作者: NJingZYuan | 来源:发表于2019-08-02 08:28 被阅读0次

    一、装饰器的基本使用

    在不改变函数源代码的前提下,给函数添加新的功能,这时就需要用到“装饰器”。

    0.开放封闭原则

    写代码要遵循开放封闭原则,它规定“已经实现的功能代码不允许被修改,但可以被扩展。”——封闭即是已经实现的功能代码块,开放是对扩展开放。

    1.装饰器本质

    装饰器本质上就是一个闭包 ,但是是一个特殊的闭包。当闭包函数有且只有一个参数,并且该参数必须是函数类型,此时闭包才称为“装饰器”。

    2.装饰器的功能特点

    1)给已有函数增加额外的功能;

    2)但是不修改已有函数的源代码;

    3)不修改已有函数的调用方式;

    3.装饰器定义格式

    # 定义装饰器

    def decorator(func):

        def wrapper(*args, **kwargs):

            要添加的额外功能

            return func(*args, **kwargs)

        return wrapper

    其中:

    1)func:是一个函数(函数作为参数);

    2)wrapper:内层闭包函数,调用了func;

    3)return wrapper:外层函数decorator返回值为内层函数wrapper;

    4.装饰器的使用

    先定义要装饰的函数:

    def print_info():

        print('打印的内容……')

    为函数print_info添加功能:打印前提示,打印后提示

    # 定义装饰器

    def decorator(func):

        def wrapper(*args, **kwargs):

            print('***我要开始打印正文了***')

            func()

            print('***全文已打印完毕***')

            return func(*args, **kwargs)

        return wrapper

    # 使用装饰器:将print_info函数作为装饰器的参数

    print_info = decorator(print_info)

    print_info()

    >>> 运行结果

    ***我要开始打印正文了***

    打印的内容……

    ***全文已打印完毕***

    可以看出,1)为函数print_info添加功能成功;2)没有修改原函数;3)原函数调用方式也没变;

    装饰器内部运行原理:

    说明:

    1)print_info = decorator(print_info),此处只装饰函数,并且把装饰器的内层函数返回,并赋值给print_info,此时print_info实际已经是wrapper函数;

    2)print_info(),此处调用函数时,才正式开始执行已经添加功能的函数(wrapper函数);

    3)wrapper函数中的func()调用的是原函数,因为闭包函数能够保存它使用的外层函数的变量;

    5.注意事项

    由上面可以看出装饰原函数后,原函数print_info的函数名已经变成wrapper(print_info.__name__='wrapper')。

    为了不改变原函数的函数名,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。Python内置的装饰器functools.wraps能够实现。因此严格意义上装饰器的定义应为:

    import functools

    def decorator(func):

        @functools.wraps(func)

        def wrapper(*args, **kwargs):

            要添加的额外功能

            return func(*args, **kwargs)

        return wrapper

    6.装饰器的装饰方式

    方式一:直接调用

    func = decorator(func)  # 直接调用装饰器函数,传入函数名,并赋值给函数名

    但每次都写调用-赋值语句比较麻烦,于是Python提供了比较简单的第二种“语法糖”书写格式

    方式二:语法糖--@装饰器名字

    @decorator              # 在函数定义语句上面直接“@装饰器名字”

    def func():

        ...

    语法糖装饰是常用的装饰器装饰方式,“@decorator”就等价于func = decorator(func)。

    装饰器在模块进行加载时就会立即执行装饰过程。

    7.装饰器使用场景

    1)引入日志;

    2)函数执行时间统计;

    3)执行函数前预备处理;

    4)执行函数后清理功能;

    5)权限校验等场景;

    6)缓存;

    8.应用举例

    统计函数的执行时间:

    # 统计函数执行时间的装饰器

    import functools

    import time

    def execute_time(func):

        @functools.wraps(func)

        def wrapper(*args, **kwargs):

            # 程序开始时间点

            start = time.time()

            # 执行函数

            result = func(*args, **kwargs)

            # 程序结束时间点

            end = time.time()

            # 计算执行时间

            execution_time = end - start

            print('{}执行时间:{}s'.format(func.__name__, execution_time))

            # 返回函数结果

            return result

        return wrapper

    # 定义一个简单的能够指定循环次数的函数,并装饰

    @execute_time

    def loop_count(n):

        # 循环打印

        for i in range(n):

            print('第{}次...'.format(i+1)) 

    # 看看循环1000000次需要多长时间?

    loop_count(1000000)

    >>> 运行结果:

    第1次...

    第2次...

    ...

    第1000000次...

    loop_count执行时间:6.355999231338501s

    二、通用装饰器

    要装饰的函数可能不需要传参,也可能需要传参;然后传多少个参数,是位置传参还是关键字传参,有没有返回值,这些都不能事先知道。也就是说,不可能针对性地去定义装饰器。于是,就需要定义通用的装饰器。不论被装饰的函数是否需要传参,传几个参数,怎么传参,有无返回值,通用装饰器都能使用。

    通用装饰器的定义格式:

    def decorator(func):

        def wrapper(*args, **kwargs):

            func执行前要添加的额外功能

            result = func(*args, **args)

            func执行后要添加的额外功能

            return result

        return wrapper

    说明:

    1)内层闭包函数使用*args、**kwargs接收参数,能够应对所有形式的不定长传参;

    2)result为被装饰函数的返回值,在闭包函数中return返回;

    解决了传参和返回值的问题,实现了通用性。

    三、多个装饰器

    多个装饰器同时装饰一个函数

    # 装饰器1:将原函数输入内容用“()”括起

    def add_little_sign(func):

        def wrapper1(*args, **kwargs):

            origin_info = func(*args, **kwargs)

            return '(' + origin_info + ')'

        return wrapper

    # 装饰器2:将原函数输入内容用“{}”括起

    def add_big_sign(func):

        def wrapper2(*args, **kwargs):

            origin_info = func(*args, **kwargs)

            return '{' + origin_info + '}'

        return wrapper

    # 被装饰函数:打印info

    @add_big_sign      # 外层

    @add_little_sign      # 内层

    def print_info(info):

        return info

    # 调用装饰后的函数

    print(print_info('Hello World!'))

    >>> 运行结果:

    {(Hello World!)}

    可以看到,先实现了add_little_sign的功能,然后实现add_big_sign的功能。

    1)上面语法糖的装饰过程等价于:

    print_info = add_little_sign(print_info)

    print_info = add_big_sign(print_info)

    装饰过程:

    原print_info函数 --> 执行“print_info = add_little_sign(print_info)” ,使用add_little_sign装饰原函数print_ifo --> print_info = wrapper1 --> 执行“print_info = add_big_sign(print_info)” ,使用add_big_sign装饰函数print_ifo(此时函数print_info已是函数wrapper1) --> print_info = wrapper2

    当装饰完毕后,原print_info已经变身成了wrapper2,并且此时wrapper2中储存着传入的wrapper1。那么再调用此时的print_info,执行过程为:调用装饰后的print_info --> 调用wrapper2 --> wrapper2内部调用wrapper1 --> 执行wrapper1 --> 装饰“()” --> 再装饰“{}” --> 完毕。

    2)使用多个装饰器时功能特性

    (1)一般使用语法糖方式装饰;

    (2)会先用内层的装饰,再用外层的装饰;

    多个装饰器同时使用时,相当于先将内层的装饰器包裹住原函数,再在外面用外层的装饰器包裹。

    四、带参数的装饰器

    有时装饰器也需要携带参数,以实现对要添加功能的处理。

    错误的装饰器携带参数的格式:

    def decorator(func, a):

        def wrapper(*args, **kwargs):

            要添加的额外功能

            (对参数a进行操作)

            return func(*args, **kwargs)

        return wrapper

    @decorator(2)      # 假如将参数a设置为2

    def func():

        代码块...

    >>> 运行结果:

    TypeError: 'int' object is not callable   

    因为装饰器只能接收一个参数,并且该参数必须是可调用的函数对象。

    正确的装饰器携带参数的格式:

    def create_decorator(a):

        def decorator(func):

            def wrapper(*args, **kwargs):

                要添加的额外功能

                (对参数a进行操作)

                return func(*args, **kwargs)

            return wrapper

        return decorator

    @create_decorator(2)      # 假如将参数a设置为2

    def func():

        代码块...

    >>> 运行结果:

    会正常运行,不会再抛出异常   

    上述代码块,定义了一个返回装饰器的闭包,通过闭包能够存储外层函数变量的特性保存住外层函数的参数,然后在装饰器中进行处理。

    装饰过程等价于:

    decorator = create_decorator(2)    # 传入 参数2,被储存至返回的装饰器内

    func = decorator(func)      # 这步开始才是真正的装饰器装饰过程

    ...

    五、类装饰器

    装饰器还有一种特殊的定义方法,就是通过定义一个类来定义装饰器。

    class Decorator(object):

        def __init__(self, func):

            # 初始化操作在此完成

            self.__func = func

        # 实现__call__方法,表示对象是一个可调用对象,可以像调用函数一样进行调用。

        # 重写类的可调用魔法方法

        def __call__(self, *args, **kwargs):

            # 添加装饰功能

            要添加的额外功能

            self.__func()

    @Decorator

    def comment():

        print("发表评论")

    comment()

    说明:

    在定义类的初始化方法时,定义一个私有实例属性,此属性是用来接收要装饰的函数对象;然后重写类的__call__方法,使对象被调用时实现原函数功能的基础上增加新功能。(拥有__call__方法能够使对象被调用,“对象()”会自动执行对象的__call__方法。)

    上述装饰器装饰过程等价于:

    comment = Decorator(comment)    # 传入comment进行实例化对象

    comment()      # 此步将执行类的实例方法__call__,从而实现了增加的功能

    传入原函数comment,Decorator接收并实例化出一个对象 --> comment = 实例化的对象 --> comment() --> 执行实例对象的__call__方法 --> 完成装饰。

    相关文章

      网友评论

        本文标题:Python - 装饰器

        本文链接:https://www.haomeiwen.com/subject/eqkkdctx.html