如何理解Python装饰器

如何理解Python装饰器?不少学员对此都有疑问,那么上海尚学堂python培训这篇文章就给予答复。python

1、预备知识

首先要理解装饰器,首先要先理解在 Python 中很重要的一个概念就是:“函数是 First Class Member” 。这句话再翻译一下,函数是一种特殊类型的变量,能够和其他变量同样,做为参数传递给函数,也能够做为返回值返回,上海python培训。函数

def abc():
    print("abc")
 
def abc1(func):
    func()
abc1(abc)

  

这段代码的输出就是咱们在函数 abc 中输出的 abc 字符串。过程很简单,咱们将函数 abc 做为一个参数传递给 abc1 ,而后,在 abc1 中调用传入的函数
再来看一段代码oop

def abc1():
    def abc():
        print("abc")
    return abc
abc1()()

  

这段代码输出和以前的同样,这里咱们将在 abc1 内部定义的函数 abc 做为一个变量返回,而后咱们在调用 abc1 获取到返回值后,继续调用返回的函数。
好了,咱们再来作一个思考题,实现一个函数 add ,达到 add(m)(n) 等价于 m+n 的效果。这题若是把以前的 First-Class Member 这一律念理清楚后,咱们便能很清楚的写出来了学习

def add(m):
    def temp(n):
        return m+n
    return temp
print(add(1)(2))

  

嗯,这里输出就是 3 。翻译

2、正说Python装饰器

看了前面的预备知识后,咱们即可以开始今天的主题了code

一、先来看一个需求吧

如今咱们有一个函数路由

def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result

如今咱们要给这个函数加上一些代码,来计算这个函数的运行时间。
咱们大概一想,写出了这样的代码字符串

import time
def range_loop(a,b):
    time_flag=time.time()
    for i in range(a,b):
        temp_result=a+b
    print(time.time()-time_flag)
    return temp_result

先且不论,这样计算时间是否是准确的,如今咱们要给以下不少函数加上一个时间计算的功能input

import time
def range_loop(a,b):
    time_flag=time.time()
    for i in range(a,b):
        temp_result=a+b
    print(time.time()-time_flag)
    return temp_result
def range_loop1(a,b):
    time_flag=time.time()
    for i in range(a,b):
        temp_result=a+b
    print(time.time()-time_flag)
    return temp_result
def range_loop2(a,b):
    time_flag=time.time()
    for i in range(a,b):
        temp_result=a+b
    print(time.time()-time_flag)
    return temp_result

咱们初略一想,嗯,Ctrl+C,Ctrl+V。emmmm 好了,如今大家不以为这段代码特别脏么?咱们想让他变得干净点怎么办?
咱们想了想,按照以前说的 First-Class Member 的概念。而后写出了以下的代码学习资料

import time
def time_count(func,a,b):
    time_flag=time.time()
    temp_result=func(a,b)
    print(time.time()-time_flag)
    return temp_result
   
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop1(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop2(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
time_count(range_loop,a,b)
time_count(range_loop1,a,b)
time_count(range_loop2,a,b)

嗯,看起来像那么回事,好了好了,咱们如今新的问题又来了,咱们如今是假设,咱们全部函数都只有两个参数传入,那么如今若是想支持任意参数的传入怎么办?咱们眉头一皱,写下了以下的代码

import time
def time_count(func,*args,**kwargs):
    time_flag=time.time()
    temp_result=func(*args,**kwargs)
    print(time.time()-time_flag)
    return temp_result
   
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop1(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop2(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
time_count(range_loop,a,b)
time_count(range_loop1,a,b)
time_count(range_loop2,a,b)

好了,如今看起来,有点像模像样了,可是咱们再想一想,这段代码实际上改变了咱们的函数调用方式,好比咱们直接运行 range_loop(a,b) 仍是没有办法获取到函数执行时间。那么如今咱们若是不想改变函数的调用方式,又想获取到函数的运行时间怎么办?
很简单嘛,替换一下不就行了

import time
def time_count(func):
    def wrap(*args,**kwargs):
        time_flag=time.time()
        temp_result=func(*args,**kwargs)
        print(time.time()-time_flag)
        return temp_result
    return wrap
   
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop1(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
def range_loop2(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
range_loop=time_count(range_loop)
range_loop1=time_count(range_loop1)
range_loop2=time_count(range_loop2)
range_loop(1,2)
range_loop1(1,2)
range_loop2(1,2)

emmmm,这样看起来感受舒服多了?既没有改变原有的运行方式,也输出了函数运行时间。
可是。。。大家不以为手动替换太恶心了么???喵喵喵???还有什么能够简化下的么??
好了,Python 知道咱们是爱吃糖的孩子,给咱们提供了一个新的语法糖,这也是今天的男一号,Decorator 装饰器

二、说说 Decorator

咱们前面已经实现了,在不改变函数特性的状况下,给原有的代码新增一点功能,可是咱们也以为这样手动的替换,太恶心了,是的 Python 官方也以为这样很恶心,因此新的语法糖来了
咱们上面的代码能够写成这样了

import time
def time_count(func):
    def wrap(*args,**kwargs):
        time_flag=time.time()
        temp_result=func(*args,**kwargs)
        print(time.time()-time_flag)
        return temp_result
    return wrap
@time_count   
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
@time_count
def range_loop1(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
@time_count
def range_loop2(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
range_loop(1,2)
range_loop1(1,2)
range_loop2(1,2)

  

哇,写到这里,你是否是恍然大悟!まさか???是的,其实 @ 符号实际上是一个语法糖,他将咱们以前的手动替换的过程交给了环境执行。好了用人话描述下,@ 的做用是将被包裹的函数做为一个变量传递给装饰函数/类,将装饰函数/类返回的值替换本来的函数。
@decorator
def abc():

pass

如同前面所讲的同样,其实是发生了一个特殊的替换过程 abc=decorator(abc) ,好了咱们来作几个题来练习下吧?

def decorator(func):
    return 1
@decorator
def abc():
    pass
abc()

  

这段代码会发生什么?答:会抛出异常。为啥啊?答:由于装饰的时候发生了替换,abc=decorator(abc) ,替换后 abc 的值为 1 。整数默认不能做为一个函数进行调用。

def time_count(func):
    def wrap(*args,**kwargs):
        time_flag=time.time()
        temp_result=func(*args,**kwargs)
        print(time.time()-time_flag)
        return temp_result
    return wrap
 
def decorator(func):
    def wrap(*args,**kwargs):
        temp_result=func(*args,**kwargs)
        return temp_result
    return wrap
 
def decorator1(func):
    def wrap(*args,**kwargs):
        temp_result=func(*args,**kwargs)
        return temp_result
    return wrap
 
@time_count
@decorator
@decorator1   
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result
  

这段代码怎么替换的?答:time_count(decorator(decorator1(range_loop)))
嗯,如今是否是对装饰器什么的有了基本的了解?

三、扩展一下

如今,我想修改下前面写的 time_count 函数,让他支持传入一个 flag 参数,当 flag 为 True 的时候,输出函数运行时间,为 False 的时候不输出时间
咱们一步步来,咱们先假设新的函数叫作 time_count_plus
咱们想实现的效果是这样的

@time_count_plus(flag=True)
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result

  

嗯,咱们看了下,首先咱们调用了 time_count_plus(flag=True) 一次,将它返回的值做为一个装饰函数来替换 range_loop ,OK 那么咱们首先 time_count_plus 要接收一个参数,返回一个函数对吧

def time_count_plus(flag=True):
    def wrap1(func):
        pass
    return wrap1

好了,如今返回了一个函数来做为装饰函数,而后咱们说了 @ 其实触发了一次替换过程,好那么咱们如今的替换是否是 range_loop=time_count_plus(flag=True)(range_loop) 好了,如今你们应该很清楚了,咱们在 wrap1 里面是否是还应该有一个函数并返回?
嗯,最终的代码以下

def time_count_plus(flag=True):
    def wrap1(func):
        def wrap2(*args,**kwargs):
            if flag:
                time_flag=time.time()
                temp_result=func(*args,**kwargs)
                print(time.time()-time_flag)
            else:
                temp_result=func(*args,**kwargs)
            return temp_result
        return wrap2
    return wrap1
@time_count_plus(flag=True)
def range_loop(a,b):
    for i in range(a,b):
        temp_result=a+b
    return temp_result

是否是这样就清楚多啦!

四、再扩展一下

好了,咱们如今有新的需求来了

m=3
n=2
def add(a,b):
    return a+b
 
def sub(a,b):
    return a-b
 
def mul(a,b):
    return a*b
 
def div(a,b):
    return a/b

如今咱们有字符串 a , a 的值可能为 +、-、*、/ 那么如今,咱们想根据 a 的值来调用对应的函数怎么办?
咱们煎蛋一想,嗯,逻辑判断嘛

m=3
n=2
def add(a,b):
    return a+b
 
def sub(a,b):
    return a-b
 
def mul(a,b):
    return a*b
 
def div(a,b):
    return a/b
a=input('请输入 + - * / 中的任意一个\n')
if a=='+':
    print(add(m,n))
elif a=='-':
    print(sub(m-n))
elif a=='*':
    print(mul(m,n))
elif a=='/':
    print(div(m,n))

  

可是这段代码,if else 是否是太多了点?咱们仔细一想,用一下 First-Class Member 的特性,而后配合 dict 实现操做符和函数之间的关联。

m=3
n=2
def add(a,b):
    return a+b
 
def sub(a,b):
    return a-b
 
def mul(a,b):
    return a*b
 
def div(a,b):
    return a/b
func_dict={"+":add,"-":sub,"*":mul,"/":div}
a=input('请输入 + - * / 中的任意一个\n')
func_dict[a](m,n)

  

emmmm,看起来不错啊,可是咱们注册的过程能不能再简化一点? 嗯,这个时候装饰器语法特性就能用上了

m=3
n=2
func_dict={}
def register(operator):
    def wrap(func):
        func_dict[operator]=func
        return func
    return wrap
@register(operator="+")
def add(a,b):
    return a+b
@register(operator="-")
def sub(a,b):
    return a-b
@register(operator="*")
def mul(a,b):
    return a*b
@register(operator="/")
def div(a,b):
    return a/b
 
a=input('请输入 + - * / 中的任意一个\n')
func_dict[a](m,n)

嗯,还记得咱们前面说的使用 @ 语法的时候,其实是触发了一个替换的过程么?这里就是利用这一特性,在装饰器触发的时候,注册函数映射,这样咱们直接根据 'a' 的值来获取函数处理数据。另外请注意一点,咱们这里没有必要修改原函数,因此咱们没有必要写第三层的函数。
若是有熟悉 Flask 同窗就知道,在调用 route 方法注册路由的时候,也是使用了这一特性 。

3、总结

其实全文下来,你们应该能知道这样一点东西。Python 中的装饰器实际上是 First-Class Member 概念的更进一层应用,咱们将函数传递给其他函数,包裹上新的功能后再行返回。@ 其实只是将这样一个过程进行了简化而已。

感谢您阅读,欢迎评论,更多文章或获取python学习资料请点击参看:上海python培训

相关文章
相关标签/搜索