理解python装饰器@,最详细简单的教程

前言

在学python装饰器的时候,网上的文章一般就是前面看得懂然后突然就看不懂了,也可能是脑子不好。所以写下来方便自己忘记以后查阅,也帮助一下后来者更好的理解python装饰器。

正文

python装饰器的作用

简单来说就是在不改动原有函数的基础上为这个函数添加新的功能。应用场景:插入日志,登录授权等。我没用过我也不知道,但是以后去企业工作了肯定是用得上的。例如老板让你添加新的功能,你总不能改动函数里面的代码吧,毕竟企业级代码都是祖传代码,不知道多少人写过的,你一不小心动了什么不该动的东西很有可能让程序崩溃,所以这时候装饰器的作用就显得尤为重要了。

python装饰器的使用

python万物皆对象,既然都是对象,那函数传的参数可以是字符串对象,整数对象,当然也可以是函数对象。

def wrapper(func):
    def inner():
        print("假设在函数执行需要打印我")
        re = func()
        print("假设在函数完成后需要打印我")
        return re
    return inner

def func():
    print("in func")
    return "func return"

func = wrapper(func)

print(func)
print(func())

"""
运行结果
<function wrapper.<locals>.inner at 0x0000023344A3F510>
假设在函数执行需要打印我
in func
假设在函数完成后需要打印我
func return
"""

这段代码估计大家都能看得懂,将func函数当作参数传给wrapper函数,在func函数执行前后对他进行一些操作。这就是python装饰器最简单的用法。

python语法糖@的使用

语法糖没有增加新功能,只是换了一种写法,没有它不影响你程序的实现。

在被装饰函数前写@wrapper相当于func=wrapper(func),以上代码也等价于下面的代码

def wrapper(func):
    def inner():
        print("假设在函数执行需要打印我")
        re = func()
        print("假设在函数完成后需要打印我")
        return re
    return inner

@wrapper
def func():
    print("in func")
    return "func return"


print(func)
print(func())

不过使用@语法糖有一个不同的地方就是无论你有没有调用func函数他都会执行装饰函数(也就是wrapper函数),它是在加载模块的时候就执行了@wrapper这个操作(可以理解为func=wrapper(func))。所以你如果在装饰器函数中不止是返回一个函数对象,而是还做了某些操作(比如打印、程序休眠等),并且你还不调用被装饰函数,可能就会导致程序的运行出现bug(不过一般人不会这么做,我也是无意中发现的)。

装饰器传参数

如果你理解了上面所讲的那么传参数也能轻易的理解,看个例子

def wrapper(func):
    def inner(a,b,*args,**kwargs):
        print("假设在函数执行需要打印我")
        re = func(a,b,*args,**kwargs)
        print("假设在函数完成后需要打印我")
        return re
    return inner

@wrapper
def func(a,b,*args,**kwargs):
    print("in func")
    print(a,b)
    print(args,kwargs)
    return "func return"


print(func)
print(func(1,2,3,4,5,k=5,j=6))

"""
运行结果
<function wrapper.<locals>.inner at 0x0000020A61FDF510>
假设在函数执行需要打印我
in func
1 2
(3, 4, 5) {'k': 5, 'j': 6}
假设在函数完成后需要打印我
func return
"""

很容易理解嘛,运行func就相当于是运行wrapper函数里面的inner,前两个参数给是给a,b的,后面的动态传参,没键值的以元组形式给args,有键值的以字典形式给kwargs。

看到这里你可能觉得你懂了,不要急我们在看一下下面这个例子

多层嵌套的装饰器

import time                                 

def timmer(*args,**kwargs):
    print(args,kwargs)
    def wrapper(f):
        print(args,kwargs)
        def inner(*args,**kwargs):
            print(args,kwargs)
            if flag:                         
                start_time = time.time()             
                ret = f(*args,**kwargs)             
                time.sleep(0.3)                 
                end_time = time.time()             
                print('此函数的执行效率%f' % (end_time-start_time)) 
            else:
                ret = f(*args, **kwargs)
            return ret                         
        return inner                         
    return wrapper                         
flag = True                                

@timmer(flag,2,3)                                                          
def func1(*args,**kwargs):
    return 666                             

#func1 = timmer(flag,2,3)(func1)
print(func1(1,2))
"""
运行结果
(True, 2, 3) {}
(True, 2, 3) {}
(1, 2) {}
此函数的执行效率0.300833
666
"""

如果没注意按照之前的思路走,func1 = wrapper,func1(1,2) = wrapper(1,2),但是这程序的运行结果跟我们想的完全不一样,从运行结果很容易知道程序已经调用了inner()函数了,但是wrapper函数只是返回inner,要调用inner函数应该是func1(1,2)()这样才对。然后与之前代码不同的地方找一下问题出在哪,细心的小伙伴应该能发现,之前使用语法糖@后面只是接对象地址,这回接的像是函数的调用。没错,为题就在这。

正确思路

其实我们就把@timmer(flag,2,3)理解为:先调用timmer(flag,2,3)函数返回一个对象wrapper,然后再执行@wrapper。根据之前所学,可以使用func1 = timmer(flag,2,3)(func1)替代@timmer(flag,2,3),运行结果一样。需要注意的是flag是一个全局变量,虽然我们没有传参数,但还是可以使用。

(有一个地方没有搞清楚,在wrapper里面打印args和kwargs能打印出传给timmer的值,如果有小伙伴知道望告知,或者我搞清楚了再更新)

然后还有一些多个装饰器装饰一个函数的、wraps模块使用的,这些没什么难点很好理解,网上有很多我就不在赘述了,可以看这里,文中部分代码来自这篇文章,这也是我目前看到的讲得最好的一篇。

 

总结

简单来说在func1前加@wrapper就等于在程序开始加载模块的时候运行了func1=wrapper(func1),而@wrapper(1,2)就等于a=wrapper(1,2)  func1=a(func1)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值