要想彻底搞懂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装饰器,可以让你的代码更易维护,可读性也有一定提升。