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' [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混合传参 ----------------------结果: