python基础知识-10-函数装饰器

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混合传参

----------------------结果:

猜你喜欢

转载自www.cnblogs.com/machangwei-8/p/10718827.html