Python中装饰器的理解和使用

写在前面

博主在学习Python的语法过程中,对于装饰器一直没有理解透彻,近期在看Django的时候,发现到处都在用装饰器,不搞懂真的没法看下去,因此决定花点时间做个学习,并记录一下。
参考教程:

装饰器的使用

首先要明确一下,装饰器用在函数之前,假设我有个函数A,这个A是我要实现的主要功能,但是在实现这个A之前(或者之后),我还要对这个函数的输入输出做一些修改,但是我又不想去修改函数A的代码和功能,那就要用到装饰器。

理解高阶函数

进入正题,日常博主在定义函数时,函数的输入以及返回值都是明确的变量,高阶函数用的少,这里我先定义一下高阶函数:把函数作为参数传入,这样的函数称为高阶函数。

函数作为另一个函数的输入

下面我根据廖雪峰老师的官网上定义一个高阶函数来解释一下:

def add(x, y, f):
    return f(x) + f(y)

这个add函数,就是个典型的高阶函数,它的三个输入参数里面,x和y是变量,而f是个函数名,来看看怎么实现这个高阶函数的功能:

res = add(-5, 6, abs)

在这行代码里面,我将f取值为abs()函数,也就是取绝对值,res=11

函数作为另一个函数的返回值

上面讲到,高阶函数中,函数可以作为另一个函数的参数输入,同样,也能作为另一个函数的输出,也就是返回值,我们看看到底是什么意思。

def calc_sum(*args):
    ax = 0
    for n in args:
        ax = ax + n
    return ax

上面这个函数是个求和函数,返回的是你输入的所有参数的和,是个最简单的函数,如果我们在调用这个函数的时候,并不需要马上就求和,而是只要获取一下这个函数名,等需要的时候再计算,那我们可以这样写:

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

f = lazy_sum(1,2,3,4,5)

然后我们print一下f,输出下面一行信息:<function lazy_sum.<locals>.sum at 0x000002201F825708>,意思是这是一个函数,并没有return结果,为啥呢,因为我们在lazy_sum函数中返回的就是一个函数,如果要调用这个函数,直接写成f()就行了。
好吧,稀里糊涂的,反正我是理解了。

装饰器

感觉前面说了一堆废话,和装饰器没啥关系啊。别急,马上有关系了。
首先,我们有一个函数A,这个函数是我想要实现的主要功能,是下面这个样子的:

def now():
    print('2021-06-28')

打印一个时间,很简单的一个函数。但是,在使用这个函数之前,我要先打个log,就是要显示一下这个函数的名字,该怎么实现呢?
可能大家会问,你直接在这个now()里面写不就行了,这里有两个问题要解释一下,一是可能代码并不支持修改,这很正常,可能并不是所有的地方都需要这个操作;二是如果这种函数如果成千上万,每个都要加进去,未免太浪费时间。
现在,我们来定义个打log的函数:

def log(func):
    def wrapper(*args, **kw):
        print('函数名为 %s:' % func.__name__)
        return func(*args, **kw)
    return wrapper

先看log函数的输入,func,它也是个函数,然后它返回的值,wrapper,也是个函数,这个函数做了啥工作了,它打印了func函数的函数名。真的是绕,没办法,谁叫它叫高阶函数呢。
从上面的描述来看,我们可以这么来实现这个打log的功能:

def log(func):
    def wrapper(*args, **kw):
        print('函数名为 %s:' % func.__name__)
        return func(*args, **kw)
    return wrapper

def now():
    print('2021-06-28')
    
now = log(now)

在控制台里面输入now(),看看输出:
在这里插入图片描述
这就是咱们想要的输出结果。
看看廖老师是怎么解释这个东西的:

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

不知不觉,咱们已经写了一个装饰器出来了,这个装饰器函数就是log()函数,不过大家一般不这么写,Python它有自己专门的语法,大家管它叫语法糖,是用@这个符号来表示的,怎么用呢,看下面:

def log(func):
    def wrapper(*args, **kw):
        print('函数名为 %s:' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log
def now():
    print('2021-06-28')

在需要装饰的函数(也就是now())前面,写个@log,然后控制台输入now()会车,和上面的输出是一样的。也就是说,装饰器@log这个的作用与`now = log(now) 是一样的,理解这个了,就大概差不多了。
好吧,反正是整了个比较简单的装饰器出来,还有更复杂的,说是如果装饰器本身还需要传入参数,还需要再去嵌套一层函数,忒复杂了,有点绕晕了,先不解释这个问题了。

函数名的变化

写到这里有点莫名其妙,两个教程上都写了这一点,我也试图来理解这个知识点。
上面我们通过装饰器调用了now函数,我们现在想看看now的函数名,控制台执行下面这句代码:

now.__name__

输出结果为:Out[25]: 'wrapper'
有点奇怪,明明是now,为啥变成了wrapper,可能是因为返回的那个wrapper()函数名字就是’wrapper’,这样有啥问题吗?正常情况下不会,但是如果遇到有些依赖函数签名的代码执行的时候,就会报错,所以,咱们还得把这个函数名改回来,咋改的呢,Python内置的functools.wraps就是做这个的,写在哪里呢,看下面:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('函数名为:%s' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log
def now():
    print('2021-06-28')
    
now()

好吧,总体就这么多了,装饰器类似于一个补丁,就是给需要的函数打个补丁。感觉还是有点稀里糊涂的,慢慢理解吧。

猜你喜欢

转载自blog.csdn.net/u012848304/article/details/118306970
今日推荐