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装饰器学习 这篇文章中,介绍了python 中的装饰器,python内置了对装饰器的支持。面向...

  • python中的装饰器

    python装饰器详解 Python装饰器学习(九步入门) 装饰器(decorator) 就是一个包装机(wrap...

  • [译] Python装饰器Part II:装饰器参数

    这是Python装饰器讲解的第二部分,上一篇:Python装饰器Part I:装饰器简介 回顾:不带参数的装饰器 ...

  • Python中的装饰器

    Python中的装饰器 不带参数的装饰器 带参数的装饰器 类装饰器 functools.wraps 使用装饰器极大...

  • Python进阶——面向对象

    1. Python中的@property   @property是python自带的装饰器,装饰器(decorat...

  • Python 装饰器填坑指南 | 最常见的报错信息、原因和解决方

    Python 装饰器简介装饰器(Decorator)是 Python 非常实用的一个语法糖功能。装饰器本质是一种返...

  • Python装饰器

    Python装饰器 一、函数装饰器 1.无参装饰器 示例:日志记录装饰器 2.带参装饰器 示例: 二、类装饰器 示例:

  • python3基础---详解装饰器

    1、装饰器原理 2、装饰器语法 3、装饰器执行的时间 装饰器在Python解释器执行的时候,就会进行自动装饰,并不...

  • 2019-05-26python装饰器到底是什么?

    装饰器例子 参考语法 装饰器是什么?个人理解,装饰器,是python中一种写法的定义。他仍然符合python的基本...

  • 2018-07-18

    Python装饰器 装饰,顾名思义,是用来打扮什么东西的。Python装饰...

网友评论

    本文标题:Python - 装饰器

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