python基础知识-11-函数装饰器
1、装饰器学习前热身准备
1.1装饰器简介
1.2装饰器热身分析
1) def func(): pass v1 = 10 v2 = func #将函数名赋予一个变量,就和变量赋值是同理。而函数名赋予的是函数所在的内存地址 print(v1,v2) ------------结果: 10 <function func at 0x01E78D20> 2) def base(): print(1) def bar(): print(2) bar = base bar() ----------------结果: 1 #代码解析:另bar重新赋值之后,它不再指向之前的函数,而是指向base所指向的函数,
执行bar,就是执行base函数 3) def func(): def inner(): pass return inner v = func() print(v) # inner函数 --------结果: <function func.<locals>.inner at 0x00638D20> #代码解析:func返回值是inner函数名,打印v,则显示v指向inner所在的地址 4) def func(arg): def inner(): arg() return inner def f1(): print(123) v1 = func(f1) v1() ----------结果: 123 #代码分析:将f1函数名作为func的参数,执行func返回inner函数的地址。即v1是带有参数为
f1函数名的inner函数地址。执行v1函数,就是执行inner函数,并且参数为f1。inner函数内部f1()
函数执行,再去找到f1函数,看它执行打印出123,f1的返回值是None。inner的返回值是None。 5) def func(arg): def inner(): arg() return inner def f1(): print(123) return 666 v1 = func(f1) result = v1() # 执行inner函数 / f1含函数 -> 123 print(result) # None ------------------结果: 123 None #代码分析:(1)以f1函数名为参数,执行func函数。(2)func函数执行,并将返回值即内部子函数inner
的地址赋值给v1。由于func执行时实现了闭包,v1执行时的参数即为f1(3)执行v1(),即执行inner函数,
执行inner函数后并将inner的返回值None赋值给result。执行inner函数期间,是将v1的参数f1函数执行
一下,打印123,并返回666.(4)result接收的是inner的返回值None,而不是f1执行的返回值。所以
打印None,而非666。 6) def func(arg): def inner(): return arg() return inner def f1(): print(123) return 666 v1 = func(f1) result = v1() # 执行inner函数 / f1含函数 -> 123 print(result) # 666 --------------结果: 123 666 #代码分析:分析过程如5。区别在于result接收的值。这里result依旧接收v1()返回的值。
v1指向inner函数,v1()的返回值是是inner的返回值。inner的返回值是arg()参数函数的返回值。
实现了闭包的,inner的参数是f1,所以result接收的就成了f1的返回值。f1的返回值是666,
所以result接收的值是666。所以打印666.而123打印的原理如5) 7) def func(): print(1) v1 = func func = 666 v1() print(func) ------------结果: 1 666 #代码解析:func赋值给v1,v1和func共同指向func代表的函数内存地址
(相当于v1复制了一份func函数的地址信息,直接拥有找到函数的能力)。
func重新赋值,func变量指向666的内存地址。虽然func重新赋值了,但是不影响v1,执行v1,
还是执行那个函数,而func却是代表新的内容
2、装饰器学习
1) def func(arg): def inner(): print('before') v = arg() print('after') return v return inner def index(): print('123') return '666' (1)示例1: v1 = index() # 执行index函数,打印123并返回666赋值给v1. --------结果: 123 #代码分析:执行index()函数,打印123,返回值index()‘666’赋值给v1 (2)示例2: v2 = func(index) # v2是inner函数,arg=index函数 index = 666 v3 = v2() ------------结果: before 123 after #代码分析: 执行函数func,参数为index。 func返回值为子函数inner,并赋值给v2。将index重新赋值666。执行v2(),因为实现了闭包,
所以v2执行时,就相当于执行带了参数为index的inner子函数。执行inner先打印before,
执行arg()就是执行了index函数.打印123,返回666给变量v。然后回到inner函数继续往下执行。
打印after。返回666。inner的返回值666就是v2(),所以v3=“666”。 v = arg() return v 实现了执行原函数并返回原函数的返回值。但是却在函数执行之前和函数执行
之后都多做了别的操作。就是实现了给函数增添功能,这就是装饰器。 (3) v4 = func(index) index = v4 # index ==> inner index() ----------结果: before 123 after #代码分析。与2相同。将index函数传入func函数,func执行后返回inner函数地址给v4,index再接收
inner函数的地址,然后执行inner函数。由于实现了闭包,这里的args指向的是原来的index函数地址所以
执行后打印123.在函数执行前有做了其他的打印操作。 (4)装饰器的一种表示方式 index = func(index) index() -------------结果: before 123 after #代码解析:实则是将原函数index的地址作为参数传入到unc函数。然后在func函数的子函数中将这
个index原函数执行前执行后增添新的功能,利用闭包原理返回子函数名。并将子函数名重新赋值给
index函数。让新的index函数在原有基础上拥有了新的功能。 (5)装饰器的正确表示方式: def func(arg): def inner(): print("after") v = arg() return v return inner # 第一步:执行func函数并将下面的函数参数传递,相当于:func(index) # 第二步:将func的返回值重新赋值给下面的函数名。 index = func(index) @func def index(): print(123) return 666 print(index) index() -----------结果: <function func.<locals>.inner at 0x00587D20> after 123 #代码分析:由上可知,index函数指向地址为inner函数。也就是说。将函数地址传到func函数加工装饰之后
重新赋值给index函数。当这个函数再次执行时,就是找到新的函数的地址并执行。 执行过程为:执行index()函数,当看到index上面有@func时,就说明使用了装饰器,于是函数找到了func,
并将index以参数形式传入。然后将func的返回值重新赋值给index,即index = func(index)。
当index()执行时,就相当于inner()执行inner函数。inner函数内参数的执行还是指向原index。
新index是指向inner函数的。inner函数的返回值定义为原index的返回值。执行index原函数并执行原函数
前后新加的代码,实现原函数功能不变的同时,增添了新的功能。而这些过程是inner函数执行的操作,
也就是新的index的执行的操作。所以,添加了装饰器就实现在原函数不变的基础上增添新的功能。
3、装饰器的编写和使用方法
1)# 装饰器的编写
def x(func):
def y(): #定义一个装饰器,传参为要装饰的函数,返回值为子函数名。子函数名是要赋值给要装饰的函数的,将要装饰的函数重新赋予新值。。
# 前
ret = func() #装饰器定义一个子函数。子函数里执行传进去的参数即要装饰的函数。并接收要装饰的函数的返回值,作为子函数的返回值。因为
# 后 #要装饰的函数名指向了装饰器子函数的内存地址,即执行被装饰后的新的函数就是执行这个子函数。所以子函数的返回值要和原函数即被
return ret #装饰之前那个函数的返回值保持一致,子函数内部原函数被执行后的返回值作为子函数的返回值,这样就保持二者一致了。这样保证了
return y #原函数在被装饰之前和装饰之后的返回值不变,保证原函数的功能不变。在此基础之上。再到原函数执行之前和之后添加新的功能,这样就
# 装饰器的应用 #实现了保证原函数不改变的情况下,给原函数增添了新的功能(思考,原函数执行之前之后的操作是不是也可以是个函数执行的过程呢,有时间验证一下)
@x
def index():
return 10
@x
def manage():
pass
# 执行函数,自动触发装饰器了
v = index()
print(v)
#装饰器原理分析:
#定义一个装饰器,传参为要装饰的函数,返回值为子函数名。子函数名是要赋值给要装饰的函数的,将要装饰的函数重新赋予新值。。 #装饰器定义一个子函数。子函数里执行传进去的参数即要装饰的函数。并接收要装饰的函数的返回值,作为子函数的返回值。因为
#要装饰的函数名指向了装饰器子函数的内存地址,即执行被装饰后的新的函数就是执行这个子函数。所以子函数的返回值要和原函数即被
#装饰之前那个函数的返回值保持一致,子函数内部原函数被执行后的返回值作为子函数的返回值,这样就保持二者一致了。这样保证了
#原函数在被装饰之前和装饰之后的返回值不变,保证原函数的功能不变。在此基础之上。再到原函数执行之前和之后添加新的功能,这样就
#实现了保证原函数不改变的情况下,给原函数增添了新的功能(思考,原函数执行之前之后的操作是不是也可以是个函数执行的过程呢,有时间验证一下)
[1]原函数: def func(): print("小马过河") func() --------结果: 小马过河 [2]原函数加上装饰器后的代码执行 def wrapper(func): def inner(): print("我是",end="") return func() return inner @wrapper def func(): print("小马过河") func() -----------结果: 我是小马过河
2)装饰器编写格式
def 外层函数(参数):
def 内层函数(*args,**kwargs):
return 参数(*args,**kwargs)
return 内层函数
#外层函数返回值为内层函数名字。内存函数返回值为要装饰的函数的返回值(func())。内层函数要使用万能传参。内层参数返回值的参数也为万能传参。记得要添加一个外层函数的参数接收变量
#内层函数和return原函数参数要统一,参数统一的目的是为了给原来的index函数传参
实验案例一:给已知为单个传参的函数添加装饰器
[1]实验案例 没有添加装饰器有一个参数的函数 def func(args): print(args) func("小马过河") --------------结果: 小马过河 [2]添加装饰器但是装饰器没有接收参数的报错情况: def wrapper(func): def inner(): print("我是",end="") return func() return inner @wrapper def func(args): print(args) func("小马过河") ---------------结果: func("小马过河") TypeError: inner() takes 0 positional arguments but 1 was given [3]只给装饰器中inner加上一个参数或者原函数被装饰前执行的加一个参数都报错 def wrapper(func): def inner(args): print("我是",end="") return func() return inner @wrapper def func(args): print(args) func("小马过河") -----------------结果: eturn func() TypeError: func() missing 1 required positional argument: 'args'
def wrapper(func):
def inner(args):
print("我是"+args,end="")
return func()
return inner
@wrapper
def func():
print()
func("小马过河")
------------结果:
我是小马过河
#代码分析:func("小马过河")的内存地址是inner函数,inner函数有接收参数,函数运行正常。inner
内的func是原函数的地址,原函数没有传参,这里也没有传参,没有冲突,所以没有问题。如此传参都是
正常运行。
[4]给inner子函数和原函数执行都加上一个参数。 def wrapper(func): def inner(args): print("我是",end="") return func(args) return inner @wrapper def func(args): print(args) func("小马过河") ------------------结果: 我是小马过河 [5]装饰器中的两个传参的位置和func的传参的位置接收变量的名字可以不一样。但是装饰器中的两个传参的位置变量名字要一样才可以。 def wrapper(func): def inner(mcw): print("我是",end="") return func(mcw) return inner @wrapper def func(args): print(args) func("小马过河") ----------------------------结果: 我是小马过河 #总结:由上可知。给有一个参数的函数添加装饰器,在装饰器的子函数以及子函数内执行的那个函数都要加上一个参数。本人如下分析:func是有参数的,需要传参进入才能正常执行。func在子函数inner中执行func()也是需要参数才可以正确执行。而装饰后的新的func是指向inner函数的地址的,新的func执行需要参数位置,所以inner也要添加一个参数位置接收传参。可以这样理解:即inner里的参数为新的func接收参数位置,inner里面func执行的参数为旧有的func接收参数的位置。新的func和旧有的func的传递的参数是一致的。此时有个疑问,就是新的func和旧有的func的函数地址是一样的吗,下面做个验证: def wrapper(func): def inner(mcw): print("我是",end="") print(func) return func(mcw) return inner @wrapper def func(args): print(args) func("小马过河") print(func) ----------------结果: 我是<function func at 0x02137DB0> 小马过河 <function wrapper.<locals>.inner at 0x02137D20> #代码分析总结:由上可知,print(func)时,装饰器里面原函数的地址是0x02137DB0,而外面打印是新的func地址0x02137D20。二者明显不同(那么那个应该是旧有的地址呢,有时间研究一下)
实验案例二:给未知多个传参的函数添加装饰器
[1]原函数的执行情况 def func(*args): mcw="".join(args) print(mcw) func("小马过河","的弟弟") --------------结果: 小马过河的弟弟 [2]加上装饰器后传参接收的个数不够报错 def wrapper(func): def inner(mcw): print("我是",end="") return func(mcw) return inner @wrapper def func(*args): mcw="".join(args) print(mcw) func("小马过河","的弟弟") --------------------结果: func("小马过河","的弟弟") TypeError: inner() takes 1 positional argument but 2 were given [3]传参个数都设为万能传参,装饰器正常执行 def wrapper(func): def inner(*args): print("我是",end="") return func(*args) return inner @wrapper def func(*args): mcw="".join(args) print(mcw) func("小马过河","的弟弟") ----------------------结果: 我是小马过河的弟弟 实验三:**args的传参 实验四;*args和**args混合传参 ----------------------结果:
实验三:**args的传参
实验四;*args和**args混合传参
3)装饰器应用格式
@外层函数
def index():
pass
index()
#装饰器的使用就是在原函数上面添加@外层函数(即装饰器函数)。告诉这个函数是使用哪个装饰器。然后执行这个函数的时候这个函数就会找到装饰器,并在这个函数执行时按装饰器的来,本函数以及新增功能一并实现。
4)问题:为什么要加 *args, **kwargs
如果
4、带参数的装饰器
4.1带参数的装饰器案例解析
【1】没带参数时是这样的 def wrapper(func): def inner(): return [func() for i in range(0,5)] return inner @wrapper def func(): print("mcw") func() ----------------结果: mcw mcw mcw mcw mcw 【2】给装饰器中传入参数是这样的 def count(count): print("count") def wrapper(func): print("wrapper") def inner(): return [func() for i in range(0,count)] return inner return wrapper @count(5) def func(): print("mcw") ------------------结果: count wrapper #代码分析:在@count(5)的时候发生了什么呢。由上可知。执行了count函数,并将5作为参数传入参数。
打印“count”并返回wrapper函数名。这时@count(5)成了@wrapper。@wrapper执行wrapper函数
将func函数名作为参数传入函数,打印“wrapper”并返回inner函数名,然后重新赋值func指向inner函数。 【3】给装饰器传入参数后执行被装饰的函数。 def count(count): def wrapper(func): def inner(): return [func() for i in range(0,count)] return inner return wrapper @count(5) def func(): print("mcw") func() ---------------结果: mcw mcw mcw mcw mcw #代码分析:由上可知,本装饰器实现了将原函数执行5次的效果。并且次数是传入的方便修改的参数。
给实现给装饰器传入参数的方法是在装饰器函数外层上定义一个外层函数,并接收传参。此处的传参是需要传入
装饰器,想要在装饰器中使用的参数。返回值是装饰器的函数名。使用加了参数的装饰器,由原来
的@wrapper改为了@count(5)。在此过程中,@count(5)外层实现了将参数5传入装饰器,然后转
为@wrapper的操作。再往后就是装饰器的功能了,暂且不说。 #综上: [1]给装饰器里传入参数的方法:在装饰器函数外再套一个函数。这个外层函数接收的参数为想要传入装饰器的
参数。外层函数的返回值是装饰器的函数名。使用装饰器由@装饰器函数名改为@外层函数名的执行并加上参数
(即@外层函数(参数)) [2]给装饰器里传入参数的作用:装饰器是用来批量装饰函数的,如果不是每个函数都要做相同的装饰,
那么就可以在装饰器中通过传进来的参数进行判断,给对应函数进行不同的装饰。举个栗子:多个函数,
每个函数的执行此时可以在@那里定义一下,这样每个函数做了相同的装饰但是又有不同的次数了;又比如,
装饰器可以用在缓存,djong的页面缓存。在不同的页面利用传参判断每个页面缓存时间是多少。 [3]一个装饰器根据不同函数实现增添不同的功能。。方法:添加传参。然后做判断。(两拨函数做差异化装饰,
可写两个装饰器,也可以写一个带传参的装饰器)
[4]带传参的装饰器,可以做对函数进行批量装饰,又能做到差异化装饰。
4.2、总结:
# 第一步:执行 ret = xxx(index) # 第二步:将返回值赋值给 index = ret @xxx def index(): pass # 第一步:执行 v1 = uuu(9) # 第二步:ret = v1(index) # 第三步:index = ret @uuu(9) def index(): pass # ################## 普通装饰器 ##################### def wrapper(func): def inner(*args,**kwargs): print('调用原函数之前') data = func(*args,**kwargs) # 执行原函数并获取返回值 print('调用员函数之后') return data return inner @wrapper def index(): pass # ################## 带参数装饰器 ##################### def x(counter): def wrapper(func): def inner(*args,**kwargs): data = func(*args,**kwargs) # 执行原函数并获取返回值 return data return inner return wrapper @x(9) def index(): pass
5、装饰器的应用之一 批量计算函数执行时间
装饰器:在不改变原函数内部代码的基础上,在函数执行之前和之后自动执行某个功能。
import time def wrapper(func): def inner(): start_time = time.time() v = func() end_time = time.time() print(end_time-start_time) return v return inner @wrapper def func1(): time.sleep(2) print(123) @wrapper def func2(): time.sleep(1) print(456) func1() func2() -------------结果: 123 2.0001144409179688 456 1.0000572204589844 #代码分析:写一个装饰器,将每一个使用装饰器的函数,在原函数前面加一个时间,在原函数后面再加时间。
二者之差就是函数的执行时间。由于这里函数执行太快,于是用sleep来加长原函数的执行时间。
6、装饰器总结:
目的:在不改变原函数的基础上,再函数执行前后自定义功能。
应用场景:想要为函数扩展功能时,可以选择用装饰器。