Python语法糖装饰器---由浅入深(看一遍就懂系列)

python装饰器就是用于拓展原来函数功能的一种函数

  • 特别之处:1.参数是一个函数;2.返回值是一个函数
  • 装饰器好处:使用python装饰器的可以在不用更改原函数的代码前提下给函数增加新的功能

文章目录

  • 为什么需要装饰器
  • 带有固定参数的装饰器
  • 带有不固定参数的装饰器函数
  • 装饰器的原理是什么?
  • 一个装饰器函数执行顺序
  • 两个装饰器函数执行顺序
  • 多个装饰器函数执行顺序
  • 带返回值的函数进行装饰
  • 通用装饰器
  • 类装饰器
  • 装饰器的用途

为什么需要装饰器

首先,我们来看一个简单的示例:

    1. 定义一个函数a
    1. 我们需要获取函数a的运行时间
    1. 方法1:篡改原函数a,参照篡改后的函数b
    1. 方法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()

输出结果

...开始验证权限...
...权限验证通过...

代码原理及执行循序如下:

  1. 当运行该程序代码时,执行到@deco时,就会调用deco函数,发现该函数是一个装饰函数,向下继续执行
  2. 此时,被装饰的函数func_a会作为参数传递给deco函数,即deco中的参数func=func_a
  3. 然后开始执行内部函数wrapper,里面有一个打印语句,和一个func函数
  4. func_a作为参数传进来,实际最终执行的是func_a(),但该处并不是把func_a函数下的代码运行出来
  5. 而是,deco函数返回值是内部函数wrapper,该处返回的是已经加了装饰的func_a函数了
  6. 原始函数func_a函数的地址已经指向了deco.wrapper的函数地址
    装饰后的func_a = deco(原始函数func_a)
  7. 因此,主代码执行@deco后,再执行func_a()(该func_a已经被装饰了),就会指向deco.wrapper的函数地址
  8. 实际代码输出结果就是:执行deco.wrapper函数,先打印出开始权限认证,然后执行func(),
  9. 也就是被当成参数传进来的原始函数func_a,打印出权限验证通过
    经过上述步骤,func_a就被装饰了

一个装饰器函数执行顺序

def deco(func):
    print("装饰器开始对原始函数进行装饰:")
    def wrapper():
        print("...开始验证权限...")
        func()
    return wrapper

@deco
def func_a():
    print("...权限验证通过...")

func_a()

输出结果

装饰器开始对原始函数进行装饰:
...开始验证权限...
...权限验证通过...

单个装饰器函数执行顺序:

  1. 主代码执行的时候,执行到@deco就对func_a开始装饰了,因此打印出:装饰器开始对原始函数进行装饰:
  2. 相当于执行以下代码 func_a = deco(func_a)
  3. 经过内函数装饰之后,返回内函数,此时原始函数func_a的地址实际已经指向了deco.wrapper函数的地址
  4. 相当于已经有了一个新的func_a函数,主代码执行func_a()函数,实际上是执行deco.wrapper函数
  5. 从而,首先打印出:…开始验证权限…
  6. 接着执行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中,经常用闭包来实现面向对象编程

  • 实现单利模式!! 其实这也是装饰器的应用。单利模式毕竟比较高大,
    需要有一定项目经验才能理解单利模式到底是干啥用的,我们就不探讨了。

博主主页:https://blog.csdn.net/u011318077

博主GITHUB: https://github.com/FangbaiZhang

欢迎留言讨论

  • 19
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值