深入理解 python 装饰器的装饰过程

深入理解 python 装饰器的装饰过程

最近有同学问在问关于 python 中装饰器的问题,说不太理解装饰器的装饰过程,那么在下面通过一个小白的故事,来给大家深入讲解一下装饰器的整个实现过程的。

讲装饰器之前我们首先来了解一下开放封闭原则,也是面向对象编程的核心原则

**开放封闭原则:**软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。

装饰器有什么作用:装饰器可以在不更改原功能代码的情况下,可以扩展新的功能,这也就是我们为什么要学习装饰器了。

小白和装饰器的故事

小白是一个刚毕业的程序员,进公司的第一个月,就被分到了一个项目组中,负责一个功能的实现,如下:

def func_1():
    print('已实现的功能一')
    #此处省略2000行代码                                                                            

小白战战兢兢连续加班一个月终于完成了功能的实现,刚好到了十一周,等来了长假,可以好好休息了,小白开开心心的回去了,第二天早上八点还没睡醒,就接到老大的电话,说客户要加新需求,在原来实现的功能基础上加上一个身份校验的功能,执行每一个功能之前都必须要校验身份,上班之前就要完成。(小白此时心中一万只草泥马在奔腾,没办法,代码还是要写)。于是小白想了一下,花了 2 天时间,在自己的代码中加上了下面一段代码,然后发给了老大,

def func_1():
    print('--新增--校验功能的---')
    #此处省略功能代码。。。。
    print('已实现的功能一')
    #此处省略2000行代码   

老大打开代码一看,立刻就火气上来了,打电话给小白,问他知不知道什么是开放封闭原则?小白一脸懵逼(心里真不懂),老大给他解释道:已经实现功能的代码就不要再更改了,可以扩展,你这个把之前实现好的功能代码全部改了,如果哪天客户又提需求说校验功能不要了,你是不是还要再全部改一遍?小白想想觉得老大说的很有道理,又花了一天时间改好了代码,发给了老大。

def func_1():
    print('已实现的功能一')
    #此处省略2000行代码   
   
def func_check(func)
	print('--新增--校验功能的---')
    #此处省略功能代码。。。。
    func()
    
 # 备注:原来功能的调用的方式全部修改,参考如下:
    func_1()  -->   func_check(func1)

老大拿到代码的一看,叹了口气,立刻打电话给小白,老大说你这个调用方式一改,其他同事负责的功能中,使用到你这三个功能的地方不是全部要改吗?不过这一遍改的思路没错,你想想办法让调用方法不变就行了。于是小白花了一天时间,想了想又写好了一份代码,发给了老大?

def func_check (func):
    def check():
        print('--新增--校验功能的---')
        #此处省略功能代码。。。。
        func()
    return check

def func_1():
    print('已实现的功能一')
    #此处省略2000行代码   
  
func_1 = func_check(func_1)
"""
思路注解:将自己的原功能函数 func_1当作参数,传入func_check这个函数中,
然后将func_check中的嵌套函数check返回出来被func_1这个变量接收,
传到func_check中的原功能函数func_1,被参数func接受了。并且在嵌套函数check中调用了。
func_1=func_check(func_1)这行代码运行完了之后,原来的func_1被变量 func接收了,
而嵌套函数check被 func_1这个变量接收了,即实现了下面的效果。
"""

func = func_1
func_1 = check
"""
所以当我们调用func_1的时候,实际上是调用了check这个函数,
通过这种方式就实现在调用方式不变的情况下加上验证功能了
"""	

老大这次看到代码终于笑了,又给小白打了个电话说:这次改的很不错,原来的功能代码没改,别的同事使用该功能的调用方法也没变,不过还可以优化一下,通过装饰器写法可以让代码看起来更简洁,让小白把 func_1=func_check(func_1) 这行代码删掉,在原功能函数定义之前加上一句@func_check,就可以休息了,如下所示:

基础的装饰器

def func_check (func):
    def check():
        print('--新增--校验功能的---')
        #此处省略功能代码。。。。
        func()
    return check

@func_check
def func_1():
    print('已实现的功能一')
    #此处省略2000行代码   

虽然小白的代码在老大的指点下改好了,但是小白还是有点懵逼,老大刚刚说的这个装饰器到底是个什么?自己一点都不了解,小白为了搞懂这个装饰器,开始了他的学习。在学习装饰器的过程中,小白总结了以下几个知识点:

"""
小白在前面实现的那个函数叫做装饰器函数
装饰器函数的特点:
	函数里面嵌套函数,外层函数接收一个参数(被装饰的函数),
	外层函数返回的是内层嵌套函数,内层中调用了外部接收到的被装饰器函数。
老大说的@func_check这种写法是python中装饰器使用的语法糖,和自己那行代码的效果是一样的
@func_check   ====>  func_1=func_check(func_1)
"""

带参数的装饰器

理解了上面的装饰器之后,小白开始思考更复杂的问题,如果被装饰器的函数有参数那么改怎么办?小白为了理解,研究了好久终于弄明白了,写出了下面一段代码实现了带参数的装饰器。

def func_check (func):
    def check(ab):
        print('新增的功能:)
        func(a,b)
    return check

@func_check
def func_1(ab):
    print('已实现的功能参数相加:',a + b)

#这个时候被装饰的功能函数需要两个参数,那么在装饰器中参数如何传递呢?

#通过之前的装饰器,我们知道,被装饰器装饰了之后,原函数func_1的指向发生了改变,
#此时指向的位置是check这个函数,
#直接调用func_1并传入参数,func_1(11,22),事实上是调用了check(),并传入了两个参数,
#即check(11,22),那么在定义装饰器的时候,应该给check定义两个参数==>check(a,b),
#然后check中调用原功能函数也需要用到这个两个(a,b)参数,
#在我们可以把check接收到的参数,放到原功能函数调用的地方即==>func(a,b)
#通过上面的步骤完成了带参数装饰器的参数传递。

小白搞懂了带参数的装饰器之后,甚是开心,又想了想装饰器有没有万能的写法,既可以装饰有参数的又可以装饰没参数的呢?这个时候小白想到了函数中的不定长参数,不定长参数可以传一个或者多个,也可以不传,把装饰器中,传参数的地方改成不定长参数那不就可以实现了吗?小白快速的改了代码,然后进行了运行代码进行测试。

通用装饰器

def func_check (func):
    def check(*args**kwargs):
        print('新增的功能')
        func(*args**kwargs)
    return check

@func_check
def func_1(ab):
    print('func_1已实现的功能参数相加:',a + b)
       
@func_check
def func_2():
    print('func_1已实现的功能)

func_1()
func_2()

果然不出小白所料,通过不定长参数小白顺利的实现通用的装饰器。好了,小白和装饰器的故事就讲到这里了

小白的故事听完了,关于装饰器你理解了吗?有问题欢迎在下面提问。

回帖
请输入回帖内容 ...