【python 装饰器】深入理解python装饰器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013421629/article/details/82919269

要想彻底搞懂Python中的装饰器,除了需要有一点Python中的函数基础,还需要解决如下四个问题。当我们解决了这四个问题后,也就彻底搞懂Python中的装饰器。
1.什么是装饰器,其本质是什么?
2.装饰器有什么作用?
3.装饰器有什么使用特点(使用原则)?
4.装饰器的应用场景
提示:如果你还不知道Python中的函数,请先了解函数后,再来学习。

下面我们依次来回答。

第一部分:什么是装饰器,其本质是什么?

装饰器是什么?

  • 从字面意思我们大致可以推测出来,它的作用是用来装饰的。日常生活中,大家都见过很多装饰器,举个最简单的例子,套在iPhone外面的保护壳。保护壳的存在,并不会改变iPhone内部的功能,它存在的意义,在于增强了iPhone的抗摔性能。

  • Python中的装饰器也是一样的道理,它并不会改变被装饰对象的内部逻辑,而是通过一种无侵入的方式,让它获得一些额外的能力,比如日志记录、权限认证、失败重试等等。

  • Python装饰器看起来高深莫测,实际上它的实现原理非常简单。我们知道,在Python中一切皆对象,函数作为一个特殊的对象,可以作为参数传递给另外一个函数,装饰器的工作原理就是基于这一特性。装饰器的默认语法是使用@来调用,这实际上仅仅是一种语法糖。

  • Python中的装饰器并没有什么神秘的,其本质上就是个函数。可以用一个等式理解:装饰器 = 高阶函数 + 嵌套函数。

  • 补充:

    1.函数就是一个对象,函数名是一个指向该对象的变量。
    2.高阶函数:参数中有以函数为参数,或者返回值是函数的函数为高阶函数
    3.函数嵌套:函数里面又定义了函数。

第二部分:.装饰器有什么作用?

  • 装饰器是一个编程利器,只需一处修改,任何被装饰的对象就可以获得额外的功能。
  • 装饰器,说白了就是用来增强其他函数功能的函数。其形式,通常是在被修饰函数定义时,用@装饰器名修饰。

来个例子理解一下:

# encoding: utf-8
import datetime


# 定义一个打印日志的装饰器
def my_log(func):
    def wrapper(*args, **kwargs):
        print("print_log")
        result = func(*args, **kwargs)
        print("{}函数调用时刻:{}".format(func.__name__, datetime.datetime.now()))
        return result
    return wrapper


# 计算平方
@my_log
def cal_square(x):
    result = x*x
    print("{}  * {} = {}".format(x,x, result))
    return result



if __name__ == '__main__':
    x=2
    result=cal_square(x)

    print result

自定义了一个计算一个数平方的函数cal_square,要求调用这个函数时打印其调用日志,就可以写一个日志装饰器,假设命名为my_log函数,用来装饰所要调用的函数。

运行结果:

D:\Python27\python.exe F:/PycharmProjects/tom/装饰器的作用.py
print_log
2  * 2 = 4
cal_square函数调用时刻:2018-10-01 21:55:06.638000
4

Process finished with exit code 0
说明:
1.在定义装饰器时,入参传入了一个函数,所以装饰器是一个高阶函数,也返回了一个内层函数名。
2.特别提醒: 外层函数的返回结果是嵌套函数wrapper函数名,而不是返回wrapper()。这两个是不同的概念哦。
3.嵌套函数中的内层函数,函数名可以是任何符合Python命名规则的标识符。我这里命名wrapper,你可以自定义为其他合法的函数名。
4.为了保证原函数的调用入参不受任何影响,内层函数入参:*args, **kwargs。
5.为了保证原函数的返回值不受任何影响,我们用一个临时变量result接收,并在内层函数进行返回了result。
6.你还记得前面提到的装饰器的本质吗?
其本质上就是个函数。可以用一个等式理解:装饰器 = 高阶函数 + 嵌套函数。

第三部分:装饰器有什么使用特点(使用原则)?

  • 1.不会修改被装饰的函数的源代码。 (对函数的源代码没有任何修改,只是在原来功能的基础上额外增强函数的功能。)
  • 2.不会改变被装饰函数的调用方式。 (原来怎么调用,被装饰后依旧怎么调用。)
  • 3.不会改变被装饰函数的返回结果。

这三点使用特点非常重要,归结为:原来的函数之前怎么调用、怎么入参、什么样的返回值,使用装饰器装饰后这些都不受任何影响。也就是:我们在不对原来函数的源代码、调用形式、返回值等任何修改的情况下,增强了原来函数的功能。

4.装饰器的应用场景
应用场景非常多,开发中常见的如:

1.插入日志
2.性能测试
3.处理事务
4.开发python开源框架时,非常常用
5、连接重试

下面再来个装饰器,在工作中应该用得着,我们知道,程序跑起来后,有一些因素往往是不可控的,比如网络的连通性。断了怎么样,得重试是吧,下面我们定义一个重试机制的装饰器,可以使用在项目中。


# 定义一个打印方法耗时的装饰器
def my_time(func):
    def wrapper(*args, **keywords):
        start = time.time()
        print("print_time")
        result = func(*args, **keywords)
        end = time.time()
        t = end - start
        print("{}方法执行耗时:{:.6}秒".format(func.__name__, t))
        return result
     return wrapper

# 定义一个重试机制的装饰器
def retry(times=10):
    def outer(f):
        def inner(*args, **kwargs):
            for i in xrange(times):
                try:
                    return f(*args, **kwargs)
                except Exception as e:
                    if (i + 1) < times:
                        pass
                    else:
                        raise e

        return inner

    return outer

使用一下


import random
@retry(10)
def non_steady():
    if random.random() <= 0.5:
        # 失败的概率是 0.5
        raise Exception("died")
    else:
        # 成功的概率是 0.5
        return "survived"


kk=non_steady()

print kk
、D:\Python27\python.exe F:/PycharmProjects/tom/装饰器的作用.py
survived

Process finished with exit code
# 计算平方
@my_time
@my_log
def cal_square(x):
    time.sleep(3)
    result = x*x
    print("{}  * {} = {}".format(x,x, result))
    return result


cal_square(5)
D:\Python27\python.exe F:/PycharmProjects/tom/装饰器的作用.py
print_time
print_log
5  * 5 = 25
cal_square函数调用时刻:2018-10-01 22:19:01.435000
wrapper方法执行耗时:3.0秒

Process finished with exit code 0

最后总之使用Python装饰器,可以让你的代码更易维护,可读性也有一定提升。

猜你喜欢

转载自blog.csdn.net/u013421629/article/details/82919269