python装饰器就是用于拓展原来函数功能的一种函数
- 特别之处:1.参数是一个函数;2.返回值是一个函数
- 装饰器好处:使用python装饰器的可以在不用更改原函数的代码前提下给函数增加新的功能
文章目录
- 为什么需要装饰器
- 带有固定参数的装饰器
- 带有不固定参数的装饰器函数
- 装饰器的原理是什么?
- 一个装饰器函数执行顺序
- 两个装饰器函数执行顺序
- 多个装饰器函数执行顺序
- 带返回值的函数进行装饰
- 通用装饰器
- 类装饰器
- 装饰器的用途
为什么需要装饰器
首先,我们来看一个简单的示例:
-
- 定义一个函数a
-
- 我们需要获取函数a的运行时间
-
- 方法1:篡改原函数a,参照篡改后的函数b
-
- 方法2:不篡改原函数a, 定义一个新函数c,函数a作为参数传入,参照函数c
import time
# 原始函数
def func_a():
print("hello")
time.sleep(1)
print("world")
方法1:简单粗暴,单个函数没问题
# 方法1:直接修改函数增加获取函数运行时间的功能
def func_b():
startTime = time.time()
print("hello")
time.sleep(1)
print("world")
endTime = time.time()
msecs = (endTime - startTime)*1000
print("函数func_a运行耗时:%d ms" %msecs)
func_b()
print()
方法2:多个需求时候无力胜任
# 方法2:定义一个新函数func_c,函数func_a作为参数传入
def func_c(func_a):
startTime = time.time()
func_a()
endTime = time.time()
msecs = (endTime - startTime) * 1000
print("函数func_a运行耗时:%d ms" % msecs)
# 运行上面的func_c函数,将func_a作为参数传入
if __name__ == '__main__':
# 将原始函数func_a作为参数传入到func_c函数中
f = func_a
func_c(f)
# 函数f的名称就是func_a
print("f.__name__is:", f.__name__)
# 函数c可以实现获取运行时间的功能
# 但是如果有1000个func_a函数,则需要把func_a函数传入到func_c函数1000次
# 然后运行func_c函数1000次
# 是不是非常耗时和耗电呢?
# 看011-语法糖之装饰器_2.py
方法3:上面功能可以使用装饰器快速实现
# 用装饰器函数实现前面的功能
# 将上面函数稍加改写
import time
# 定义一个装饰函数,函数的参数是一个函数
def deco(func):
# 定义一个内部函数,实现具体的功能
def wrapper():
startTime = time.time()
func()
endTime = time.time()
msecs = (endTime - startTime) * 1000
print("原函数获得的拓展功能,原始函数func_a运行耗时:%d ms" % msecs)
# 装饰函数的返回值是内部函数的执行结果
return wrapper
# 使用@符号拓展函数功能,func_a就具有了deco函数的功能
@deco
def func_a():
print("hello")
time.sleep(1)
print("world")
if __name__ == '__main__':
# 将func_a定义为参数f
f = func_a
# f() == func_a()
f()
输出结果
hello
world
原函数获得的拓展功能,原始函数func_a运行耗时:1015 ms
经过上面几个小例子,你应该明白装饰器的作用了
下面我们就由浅入深来详细介绍装饰器的使用和功能
带有固定参数的装饰器
import time
# 定义一个装饰函数,函数的参数是一个函数
def deco(func):
# 定义一个内部函数,实现具体的功能,
# 原始函数带有参数,该处传入参数到该内部函数
def wrapper(a, b):
startTime = time.time()
func(a, b)
endTime = time.time()
msecs = (endTime - startTime) * 1000
print("原函数获得的拓展功能,原始函数func_a运行耗时:%d ms" % msecs)
# 装饰函数的返回值是内部函数的执行结果
return wrapper
# 使用@符号拓展函数功能,func_a就具有了deco函数的功能
@deco
def func_a(a, b):
print("带有固定参数的装饰器演示:")
time.sleep(1)
print("传入的参数求和:%d" % (a + b))
if __name__ == '__main__':
func_a(1, 2)
输出结果
带有固定参数的装饰器演示:
传入的参数求和:3
原函数获得的拓展功能,原始函数func_a运行耗时:1000 ms
带有不固定参数的装饰器函数
# 带有不固定参数的装饰器函数
# args和kwargs大展身手时候到了
# 011-语法糖之装饰器_3.py的参数进行修改
import time
# 定义一个装饰函数,函数的参数是一个函数
def deco(func):
# 定义一个内部函数,实现具体的功能,
# 原始函数带有不定参数,该处传入不定参数到该内部函数
def wrapper(*args, **kwargs):
startTime = time.time()
func(*args, **kwargs)
endTime = time.time()
msecs = (endTime - startTime) * 1000
print("原函数获得的拓展功能,原始函数func_a运行耗时:%d ms" % msecs)
# 装饰函数的返回值是内部函数的执行结果
return wrapper
# 使用@符号拓展函数功能,func_a就具有了deco函数的功能
# 先传入2个参数
@deco
def func_a(a, b):
print("带有不定参数2个的装饰器演示:")
time.sleep(1)
print("传入的不定参数求和:%d" % (a + b))
# 传入3个参数
@deco
def func_b(a, b, c):
print("带有不定参数3个的装饰器演示:")
time.sleep(1)
print("传入的不定参数求和:%d" % (a + b + c))
if __name__ == '__main__':
func_a(1, 2)
func_b(1, 2, 3)
输出结果
带有不定参数2个的装饰器演示:
传入的不定参数求和:3
原函数获得的拓展功能,原始函数func_a运行耗时:1000 ms
带有不定参数3个的装饰器演示:
传入的不定参数求和:6
原函数获得的拓展功能,原始函数func_a运行耗时:1000 ms
前面已经讲了装饰器的简单实例
那么究竟装饰器的原理是什么?
先看下面这个例子:
# 先看下面这个例子:
def deco(func):
def wrapper():
print("...开始验证权限...")
func()
return wrapper
@deco
def func_a():
print("...权限验证通过...")
func_a()
输出结果
...开始验证权限...
...权限验证通过...
代码原理及执行循序如下:
- 当运行该程序代码时,执行到@deco时,就会调用deco函数,发现该函数是一个装饰函数,向下继续执行
- 此时,被装饰的函数func_a会作为参数传递给deco函数,即deco中的参数func=func_a
- 然后开始执行内部函数wrapper,里面有一个打印语句,和一个func函数
- func_a作为参数传进来,实际最终执行的是func_a(),但该处并不是把func_a函数下的代码运行出来
- 而是,deco函数返回值是内部函数wrapper,该处返回的是已经加了装饰的func_a函数了
- 原始函数func_a函数的地址已经指向了deco.wrapper的函数地址
装饰后的func_a = deco(原始函数func_a) - 因此,主代码执行@deco后,再执行func_a()(该func_a已经被装饰了),就会指向deco.wrapper的函数地址
- 实际代码输出结果就是:执行deco.wrapper函数,先打印出开始权限认证,然后执行func(),
- 也就是被当成参数传进来的原始函数func_a,打印出权限验证通过
经过上述步骤,func_a就被装饰了
一个装饰器函数执行顺序
def deco(func):
print("装饰器开始对原始函数进行装饰:")
def wrapper():
print("...开始验证权限...")
func()
return wrapper
@deco
def func_a():
print("...权限验证通过...")
func_a()
输出结果
装饰器开始对原始函数进行装饰:
...开始验证权限...
...权限验证通过...
单个装饰器函数执行顺序:
- 主代码执行的时候,执行到@deco就对func_a开始装饰了,因此打印出:装饰器开始对原始函数进行装饰:
- 相当于执行以下代码 func_a = deco(func_a)
- 经过内函数装饰之后,返回内函数,此时原始函数func_a的地址实际已经指向了deco.wrapper函数的地址
- 相当于已经有了一个新的func_a函数,主代码执行func_a()函数,实际上是执行deco.wrapper函数
- 从而,首先打印出:…开始验证权限…
- 接着执行wrapper中的func(),此时调用的才是原始函数func_a,打印出:…权限验证通过…
两个装饰器函数执行顺序
看下面示例
# 两个装饰器函数执行顺序
def deco1(func):
print("001装饰器开始对函数进行装饰")
def wrapper():
print("001装饰器内函数开始执行")
return " (001) " + func() + " (001) "
return wrapper
def deco2(func):
print("002装饰器开始对函数进行装饰")
def wrapper():
print("002装饰器内函数开始执行")
return " [002] " + func() + " [002] "
return wrapper
@deco2
@deco1
def func_a():
print("...003原始函数终于执行了...")
return "Hello Python Decorator"
ret = func_a()
print(ret)
print("\n--------------------------\n")
print(type(func_a))
print(type(ret))
print(func_a)
输出结果
001装饰器开始对函数进行装饰
002装饰器开始对函数进行装饰
002装饰器内函数开始执行
001装饰器内函数开始执行
...003原始函数终于执行了...
[002] (001) Hello Python Decorator (001) [002]
--------------------------
<class 'function'>
<class 'str'>
<function deco2.<locals>.wrapper at 0x000002BABCFE2A60>
上面两个装饰器的执行顺序如下:
# 上面函数的执行顺序是,主代码运行后,依次运行@deco2,@deco1:
# 1. 运行主代码,首先运行到@deco2时,发现是一个装饰器函数,需要对后面的函数进行装饰
# 2. 因此向下执行寻找deco2中的参数func,但是下一行代码是@deco1,并不是一个函数名,而又是一个装饰器函数
# 3. 此时,@deco2暂时执行,执行接下来的装饰器@deco1,然后向下执行找到函数名func_a
# 4. func_a作为参数传给装饰器@deco1,@deco1开始执行,从而打印出:001装饰器开始对原始函数进行装饰
# 5. 然后@deco1内部函数开始对func_a进行装饰,装饰之后,deco1返回值为内函数wrapper,
# 6. 此时,原始函数func_a已经指向了deco1的内函数wrapper的地址,即func_a = deco1.wrapper
# 7. 然后又返回继续执行@deco2,把新的func_a(@deco1装饰之后的func_a)作为参数传个deco2函数
# 8. 执行deco2函数,从而打印出:002装饰器开始对函数进行装饰,
# 9. 然后@deco2内部函数开始对新func_a进行装饰,装饰之后,deco2返回值为内函数wrapper,
# 10. 此时,新函数func_a已经又指向了deco2的内函数wrapper的地址,即func_a = deco2.wrapper
# 经过上述步骤,@deco2和@deco1都已经运行,并且func_a函数已经被两个函数进行了装饰
# 11. 继续执行代码 ret = func_a(),即运行原始函数func_a函数
# 12. 但是,func_a经过装饰后,最后指向的是deco2.wrapper,从而运行deco2.wrapper函数
# 13. 因此打印出:002装饰器内函数开始执行,同时继续执行代码:return " [002] " + func() + " [002] "
# 14. 即返回一个" [002] " + func() + " [002] ",此时func()返回值的最外层已经被装饰002
# 15. 语句中的func()执行的时候寻找到的是func_a的上一级地址deco1.wrapper(deco2.wrapper地址已经使用过了)
# 16. 上面14返回值中的func()即deco1.wrapper运行后的返回值:" (001) " + func() + " (001) "
# 17. 因此deco2.wrapper函数返回的实际是:" [002] " + " (001) " + func() + " (001) " + " [002] "
# 18. deco1.wrapper运行时,首先打印出:001装饰器内函数开始执行,
# 19. 同时继续执行代码:return " (001) " + func() + " (001) "
# 16. 这时语句中的func()才是指向的原始函数func_a的地址,执行原始函数func_a,打印出:...003原始函数终于执行了...
# 17. 原始函数func的返回值是:"Hello Python Decorator"
# 18. 因此最终func_a函数运行后的返回值是:" [002] " + " (001) " + "Hello Python Decorator" + " (001) " + " [002] "
# 19. 最后执行print(ret)即打印出经过装饰之后func_a()函数的返回值
# 总之,装饰器装饰的时候,从最靠近原始函数开始进行装饰,逐级向外进行(外函数),
# 所有装饰函数装饰结束后,再执行原始函数,此时原始函数指向的是最后一个装饰器内函数的地址,
# 内函数又外外面的装饰器开始执行,逐级向里面执行(内函数),所有内函数开始执行完了,最后开始执行原始函数下的代码
# 先里后外,再外后里,最后原始
多个装饰器装饰器函数运行顺序:
- 最靠近原始函数的装饰器@deco_1首先运行——然后运行靠近该装饰器的第二个装饰器@deco_2
- 依次启动完所有的装饰器,运行完所有装饰器中的外函数,然后启动主程序,内函数从外层向里层运行
- 注意,如果装饰器中内函数前面没有外函数,还是先运行所有的装饰器,然后运行原始函数,
import time
# 定义第一个装饰函数,函数的参数是一个函数
def deco_1(func):
# print()是装饰器包裹函数(内部函数)之前的函数,可以称为外函数
print("001这个是第一个装饰器的外函数(最靠近原始函数,最先运行):")
# 定义一个内部函数,实现具体的功能,
# 原始函数带有不定参数,该处传入不定参数到该内部函数
def wrapper(*args, **kwargs):
print("第一个装饰器包裹函数(内函数)运行开始")
func(*args, **kwargs)
print("第一个装饰器运行结束")
# 装饰函数的返回值是内部函数的执行结果
return wrapper
# 定义第二个装饰器
def deco_2(func):
print("002这个是第二个装饰器的外函数(第二个装饰器紧跟着第一个装饰器之后运行):")
# 定义一个内部函数,实现具体的功能,
# 原始函数带有不定参数,该处传入不定参数到该内部函数
def wrapper(*args, **kwargs):
print("第二个装饰器包裹函数(内函数)运行开始")
func(*args, **kwargs)
print("第二个装饰器运行结束")
# 装饰函数的返回值是内部函数的执行结果
return wrapper
def deco_3(func):
print("003这个是第三个装饰器的外函数(第三个装饰器紧跟着第二个装饰器之后运行):")
def wrapper(*args, **kwargs):
print("第三个装饰器包裹函数(内函数)运行开始")
func(*args, **kwargs)
print("第三个装饰器运行结束")
# 装饰函数的返回值是内部函数的执行结果
return wrapper
# 使用@符号拓展函数功能,func_a就具有了deco函数的功能
@deco_3
@deco_2
@deco_1
def func_a(a, b):
print("004原始函数开始运行(装饰器内函数已经全部开始运行):")
time.sleep(1)
print("原始函数的参数求和结果:%d" % (a + b))
print("-------------------------------")
return "Hello World"
if __name__ == '__main__':
func_a(1, 2)
输出结果
001这个是第一个装饰器的外函数(最靠近原始函数,最先运行):
002这个是第二个装饰器的外函数(第二个装饰器紧跟着第一个装饰器之后运行):
003这个是第三个装饰器的外函数(第三个装饰器紧跟着第二个装饰器之后运行):
第三个装饰器包裹函数(内函数)运行开始
第二个装饰器包裹函数(内函数)运行开始
第一个装饰器包裹函数(内函数)运行开始
004原始函数开始运行(装饰器内函数已经全部开始运行):
原始函数的参数求和结果:3
-------------------------------
第一个装饰器运行结束
第二个装饰器运行结束
第三个装饰器运行结束
对带返回值的函数进行装饰
def w_test(func):
def inner():
print('w_test inner called start')
func()
print('w_test inner called end')
return inner
@w_test
def test():
print('this is test fun')
return 'hello'
ret = test()
print(ret)
# test进行装饰后指向inner函数,首先打印出:w_test inner called start
# 接着执行func(),此时才是执行的原始函数test(),打印出:this is test fun
# 原始函数test()虽然有返回值字符串hello,但是inner内部没有变量接受,再将其返回
# 接着执行inner最后一句,打印出:w_test inner called end
# 最后打印出ret value is None,装饰后的test()函数运行结束
# 在inner函数中对test进行了调用,但是没有接受返回值,也没有进行返回,那么默认就是None了
输出结果
w_test inner called start
this is test fun
w_test inner called end
None
修改之后的代码
# 希望最后test()运行结束后返回字符串hello
# inner函数内接受func的返回值,inner函数最后返回该接受值
print("\n修改后的代码:")
def w_test(func):
def inner():
print('w_test inner called start')
str = func()
print('w_test inner called end')
return str
return inner
@w_test
def test():
print('this is test fun')
return 'hello'
ret = test()
print(ret)
输出结果
修改后的代码:
w_test inner called start
this is test fun
w_test inner called end
hello
通用装饰器
介绍了这么多,在实际应用中,如果针对没个类别的函数都要写一个装饰器的话,估计就累死了,那么有没有通用万能装饰器呢,答案肯定是有的,直接上代码
定义一个通用装饰器,参数采用不定参数args和kwargs
# 定义一个通用装饰器,参数采用不定参数args和kwargs
def w_test(func):
def inner(*args, **kwargs):
ret = func(*args, **kwargs)
return ret
return inner
@w_test
def test():
print('test called')
@w_test
def test1():
print('test1 called')
return 'python'
@w_test
def test2(a):
print('test2 called and value is %d ' % a)
test()
print("-------------")
ret = test1()
print(ret)
print("-------------")
test2(9)
# 上面test1函数,inner中的func执行后的返回的值给ret,inner又返回了ret,最后又返回了inner
# ret最后就等于test1()运行结束后的返回值
输出结果
test called
-------------
test1 called
python
-------------
test2 called and value is 9
类装饰器
# 装饰器函数其实是一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。
# 在python中,一般callable对象都是函数,但是也有例外。
# 比如只要某个对象重写了call方法,那么这个对象就是callable的。
class Test(object):
def __call__(self, *args, **kwargs):
print('call called')
t = Test()
print(type(t))
print(t())
# 使用类装饰函数
class Test(object):
def __init__(self, func):
print('test init')
print('func name is %s ' % func.__name__)
self.__func = func
def __call__(self, *args, **kwargs):
print('装饰器中的功能')
self.__func()
@Test
def test():
print('this is test func')
test()
# 和之前的原理一样,当python解释器执行到到@Test时,
# 会把当前test函数作为参数传入Test对象,首先调用init方法,
# 同时将test函数指向创建的Test对象,那么在接下来执行test()的时候,
# 其实就是直接对创建的对象进行调用,执行其call方法
输出结果
<class '__main__.Test'>
call called
None
test init
func name is test
装饰器中的功能
this is test func
装饰器!!!装饰器是做什么的??其中一个应用就是,我们工作中写了一个登录功能,
-
我们想统计这个功能执行花了多长时间,我们可以用装饰器装饰这个登录模块,装饰器帮我们完成登录函数执行之前和之后取时间。
-
面向对象!!!经历了上面的分析,我们发现外函数的临时变量送给了内函数。大家回想一下类对象的情况,对象有好多类似的属性和方法,所以我们创建类,用类创建出来的对象都具有相同的属性方法。闭包也是实现面向对象的方法之一。在python当中虽然我们不这样用,在其他编程语言入比如avaScript中,经常用闭包来实现面向对象编程
-
实现单利模式!! 其实这也是装饰器的应用。单利模式毕竟比较高大,
需要有一定项目经验才能理解单利模式到底是干啥用的,我们就不探讨了。