装饰器是个很用的东西,但是对于初学者却没那么好理解,这里从最简单的任务入手,手把手告诉你装饰器是什么!
任务描述:你有一个函数foo(x,y)
,输入两个参数,可以打印输出两个数的和x+y
,现在要求你给出一个新函数,使得输入两个参数,打印输出两个数的和差积商( +−×÷ + − × ÷ )。
要求:不能改动原函数foo(x,y)
这里为什么会有个不能改动原函数
的要求呢?这是因为在实际工作学习中,有改动原函数工作量会很大!举个例子,这个代码很大很长,又或是这个代码是你很早以前写的,现在记忆不清楚了,在这些情况下,想要增加函数功能,就得先把原函数读懂,然后再修改,任务量不言而喻。
这个任务是带参数的,略微有点复杂,这里埋个伏笔先不管这个任务1,我们现在从一个更简单的任务2入手,理解了这个小任务,再回过头来看我们的任务1,就迎刃而解了!
任务描述:你有一个函数bar()
,输出一句话“这是函数bar
”,现在要求你给出一个新函数,使得输出增加一句话(内容随意)。
要求:不能改动原函数foo(x,y)
我们正式开始!
这个bar()
函数太简单了,这里直接给出:
def bar():
print('这是函数bar')
如果是没有不能改动原函数
这个要求,那么增加一句话是这么操作的:在函数体里面增加代码:
def bar():
print('这是随便写的一句话')
print('这是函数bar')
现在有了不能改动原函数
这个要求,该怎么做呢?我们可以这么做:把函数bar
丢给另外一个函数,用这个另外的函数给bar
包一层代码。
来,看看这段代码能读懂不:
def dec(aaa):
def in_dec():
print('这是随便写的一句话')
aaa()
return in_dec
def bar():
print('这是函数bar')
bar = dec(bar)
bar()
这段代码的难点在于读懂函数dec(aaa)
,函数里面还有一个函数。看似可怕,但其实都是纸老虎,函数就只是函数而已,无非是复杂一点,并不是难。下面一起来读懂这段代码!
首先可以肯定的是,函数dec(aaa)
有一个返回值,这个返回值不是整数int,也不是字符串str,而是一个函数。所以,
bar = dec(bar)
这句代码做的事是:把函数bar
当做参数给了函数dec
,dec
执行完后输出(return)了一个函数(不是int,不是str),并把这个函数赋值给了bar
。所以,=
号左边的bar
是一个与原bar
函数不同的函数,是经过函数dec
处理过的新函数。
接着来,那么这个返回的函数是什么呢?看dec(aaa)
函数体内部:
def dec(aaa):
def in_dec():
print('这是随便写的一句话')
aaa()
return in_dec
可以看出,返回的是dec(aaa)
函数内部的那个函数in_dec()
,也就是说,新bar = in_dec()
,也就是说,执行新bar
等于执行in_dec()
。
那么,我们这个in_dec()
函数到底是什么呢?我们把传入dec(aaa)
的参数考虑进来,可以看出,丢给dec
一个参数(一个函数),这个丢进来的函数就成了in_dec()
的一部分。拿我们这句代码来讲:
bar = dec(bar)
就是,把原bar
丢给了dec
,dec执行的结果是,原bar
进了in_dec
函数,替换掉了aaa()
,所以,in_dec()
最终实际上等于:
def in_dec():
print('这是随便写的一句话')
bar()
而这个in_dec()
又return给了新bar
,所以,新bar
实际等于in_dec
,写的再直白一点就是:
def 新bar():
print('这是随便写的一句话')
原bar()
到这里,代码已经分析地很透彻了,我们执行一下新bar()
,结果也就很明显了:
bar()
这是随便写的一句话
这是函数bar
代码分析完成了,应该说分析的很清楚透彻了,下面开始引入我们的主角——装饰器。
刚刚的这段代码:
def dec(aaa):
def in_dec():
print('这是随便写的一句话')
aaa()
return in_dec
def bar():
print('这是函数bar')
bar = dec(bar)
bar()
在python中,可以用另外一种方式来写,意思完全一样:
def dec(aaa):
def in_dec():
print('这是随便写的一句话')
aaa()
return in_dec
@dec
def bar():
print('这是函数bar')
bar()
自己执行一下,结果确实是一样的。
可以看出,两段代码区别其实很小,无非就是用一个@dec
代替了bar = dec(bar)
。这两个的意思是一模一样的,写法不一样,无非就是格式问题,本质是一样的。
那么,这个用@dec
来给bar
增加功能的这些代码,就叫装饰器
。
其实也很好理解,装饰就是加点小玩意儿的意思嘛,这不就是给函数增加了点功能嘛,所以用来增加功能的这些代码就叫装饰器
。
所以在这里,我给装饰器下一个定义:装饰器就是在不改动原函数的前提下,以原函数为核心,给原函数增加功能的代码。
好,到这里,我们的装饰器的核心内容就讲完了。
我们可以自己做个小练习:
给以下几个函数增加一句话,内容随意:
def bar():
print('这是函数bar')
def bar2():
print('这是函数bar2')
def bar3():
print('这是函数bar3')
def bar4():
print('这是函数bar4')
答案:
def dec(aaa):
def in_dec():
print('这是随便写的一句话')
aaa()
return in_dec
@dec
def bar1():
print('这是函数bar1')
@dec
def bar2():
print('这是函数bar2')
@dec
def bar3():
print('这是函数bar3')
@dec
def bar4():
print('这是函数bar4')
bar1()
bar2()
bar3()
bar4()
这是随便写的一句话
这是函数bar1
这是随便写的一句话
这是函数bar2
这是随便写的一句话
这是函数bar3
这是随便写的一句话
这是函数bar4
在解释装饰器的时候,为了力图容易理解,有一些细节并没有说出来,现在在这里统一说明
闭包
函数的概念,在我讲的例子里,in_dec
就是一个闭包函数。闭包函数可以简单地理解为:使用了上级函数(dec
)变量的内函数。其中这个变量的问题,又涉及到LEGB原则,想要深入理解的读者可以自己再查,这里不再赘述。dec
函数的名字来源。 我们开始解决我们的任务1(其实如果前面的学懂了,这里可以不用看讲解部分了,直接看答案,当做个练习),foo(x,y)
函数非常简单,直接给出:
def foo(x, y):
print('x+y =', x+y)
为了照顾新手,从最基本的赋值方法foo = dec(foo)
讲,可以强化对原理的理解。
根据之前讲的,自己先写一下,重点注意带参数情况该怎么写,我这里给出个代码框架,参数栏我用?
代替。
def dec(aaa):
def in_dec(?):
aaa(?)
print('x-y =', x - y)
print('x*y =', x * y)
print('x/y =', x / y)
return in_dec
def foo(x, y):
print('x+y =', x+y)
foo = dec(foo)
foo(1, 2)
答案公布,两个问号的地方,都需要写x,y
def dec(aaa):
def in_dec(x, y):
aaa(x, y)
print('x-y =', x - y)
print('x*y =', x * y)
print('x/y =', x / y)
return in_dec
def foo(x, y):
print('x+y =', x+y)
foo = dec(foo)
foo(1, 2)
运行一下
x+y = 3 x-y = -1 x*y = 2 x/y = 0.5
下面解释一下参数为什么要这么写。
看dec
函数
def dec(aaa):
def in_dec(?):
aaa(?)
print('x-y =', x - y)
print('x*y =', x * y)
print('x/y =', x / y)
return in_dec
我们知道,aaa(?)
是要用原foo(x,y)
替换掉的,那么这里的aaa
肯定是要加参数的。再看in_dec(?)
,因为函数体里需要用两个参数,所以这里也要加参数。
好的,这个任务到这里基本完成了,还差最后一步:用装饰器的语法把上面的代码改写一下,也就是我们最终的答案。
def dec(aaa):
def in_dec(x, y):
aaa(x, y)
print('x-y =', x - y)
print('x*y =', x * y)
print('x/y =', x / y)
return in_dec
@dec
def foo(x, y):
print('x+y =', x+y)
foo(1, 2)
x+y = 3
x-y = -1
x*y = 2
x/y = 0.5