终于搞明白了Python装饰器,我来说说我理解的装饰器

目录

一、装饰器概述

二、装饰器的入门

1.什么是闭包

2.什么是装饰器

3.闭包与装饰器的区别

4.为何要用装饰器 

5.适用场景

 三、如何使用装饰器

1.通过案例了解装饰器的用法

2.Python装饰器执行顺序详解

四、装饰器传参 

五、装饰器返回值 

 六、通用装饰器

 七、装饰器带参数


一、装饰器概述

装饰器是一个函数,该函数是用来为其他函数添加额外的功能,就是拓展原来函数功能的一种函数。


二、装饰器的入门

1.什么是闭包

要掌握装饰器先得理解闭包,掌握了闭包以后再学装饰器就很容易了。

闭包就是外部函数中定义一个内部函数,内部函数引用外部函数中的变量,外部函数的返回值是内部函数。闭包是将函数内部和函数外部连接起来的桥梁。

举例:比如我们调用一个带有返回值的函数x,此时函数x为我们返回一个函数y,这个函数y,就被称为闭包

# 闭包
def out(i):      # 一个外层函数
    def inner(j):    # 内层函数
        return i*j
    return inner

闭包定义条件:

  • 必须有一个内嵌函数
  • 内嵌函数必须引用外部函数中的变量
  • 外部函数的返回值必须是内嵌函数

 案例:函数test_in就是闭包

def test(number):

    print("--1--")

    def test_in(number2):
        print("--2--")
        print(number+number2)

    print("--3--")
    return test_in

ret = test(100)  #先执行第3行,输出"--1--";再执行第5行,函数名(test_in),不执行函数内部;然后执行第10行,输出"--3--";然后返回(test_in)函数给(test)函数,赋值给ret对象。
print("-"*30)    #输出”------------------------------“
ret(1)           #执行(test_in)函数,执行第6行,输出"--2--";执行第7行,输出"101"(number=100,number2=1)。
ret(100)         #执行(test_in)函数,执行第6行,输出"--2--";执行第7行,输出"200"(number=100,number2=100)。
ret(200)         #执行(test_in)函数,执行第6行,输出"--2--";执行第7行,输出"300"(number=100,number2=200)。

 输出结果:

--1--
--3--
------------------------------
--2--
101
--2--
200
--2--
300

运行分析:先调用外部函数test传入默认值number,用ret去指向返回函数内部的引用。后面在调用ret的时候,就会在调用外面函数的基础上进行计算。

2.什么是装饰器

装饰器就是装饰、装修的意思,不改变原有程序的功能。比如,我家有一个房子,如果不隔音,我在墙上加一层隔音板,而不是重新再建一座房子。这样一来,房子还是原来的,只是增加了一个隔音板(装饰器),实现了房子隔音的功能。

在程序中也是一样,不会对原来的函数造成变化,还要添加新的功能,调用函数时的接口没有变化。

装饰器可以基于函数实现,也可以基于类实现,其使用方式基本是固定的。它的基本步骤为:

  1. 定义装饰函数(类)
  2. 定义业务函数
  3. 在业务函数上一行添加@装饰函数名/类名

案例:(需求:在不动原函数的基础上增加新的功能)

def w1(func):
    """装饰器函数"""
    def inner():
        func()
        print("这是添加的新功能")
    return inner

@w1  # 等同于调用函数的时候上方加上:f1 = w1(f1)
def f1():
    """业务函数"""
    print("---f1---")

@w1 # 等同于调用函数的时候上方加上:f2 = w1(f2)
def f2():
    """业务器函数"""
    print("---f2---")

f1()
f2()

输出结果:

---f1---
这是添加的新功能
---f2---
这是添加的新功能

3.闭包与装饰器的区别

闭包传递的是 变量,而装饰器传递的是 函数,除此之外没有任何区别,或者说装饰器是闭包的一种,它只是 传递函数的闭包

4.为何要用装饰器 

开放封闭原则
开放:指的是对拓展功能是开放的
封闭:指的是对修改源代码是封闭的

装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下,为被装饰对象添加新功能。

5.适用场景

函数在执行前后定义一些功能。


 三、如何使用装饰器

1.通过案例了解装饰器的用法

案例:

def w1(fn):
    """装饰器函数"""
    print("---正在装饰---")
    def inner():
        print("---正在验证---")
        fn()
    return inner

@w1 # 只要Python解释器执行到这个代码,就开始自动进行装饰,而不是等到调用的时候才装饰
def f1():
    """业务函数"""
    print("---2---")
    return "hello python"

输出结果:

---正在装饰---

运行分析:

代码执行到@w1就开始装饰了,我们并没有调用函数f1都输出了“---正在装饰---”,我们调用的是装饰后的结果。


案例:

def w1(fn):
    """装饰器函数"""
    print("---正在装饰1---")
    def inner():
        print("---正在验证---")
        fn()
    return inner

def w2(fn):
    """装饰器函数"""
    print("---正在装饰2---")
    def inner():
        print("---正在验证---")
        fn()
    return inner

@w1
@w2
def f1():
    """业务函数"""
    print("---3---")

 输出结果:

---正在装饰2---
---正在装饰1---

运行分析:

@w1在最上面,下面需要是一个函数,可下面是@w2,必须等@w2装饰完再装饰@w1,所以先输出 ---正在装饰2---,再输出 ---正在装饰1---。 

2.Python装饰器执行顺序详解

2.1案例执行

 案例:

def decorator_a(func):
    print ('Get in decorator_a')
    def inner_a(*args, **kwargs):
        print ('Get in inner_a')
        return func(*args, **kwargs)
    return inner_a

def decorator_b(func):
    print ('Get in decorator_b')
    def inner_b(*args, **kwargs):
        print ('Get in inner_b')
        return func(*args, **kwargs)
    return inner_b

@decorator_b
@decorator_a
def f(x):
    print ('Get in f')
    return x * 2

f(1)

输出结果:

Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f

 运行分析:

上面代码先定义两个函数: decotator_a, decotator_b, 这两个函数实现的功能是,接收一个函数作为参数,然后返回创建的另一个函数;

在这个创建的函数里,调用接收的函数(文字比代码绕人)。最后定义的函数 f  采用上面定义的decotator_a, decotator_b作为装饰函数。

在当我们以1为参数,调用装饰后的函数 f 后, decotator_a, decotator_b 的顺序是什么呢(这里为了表示函数执行的先后顺序,采用打印输出的方式来查看函数的执行顺序)?

如果不假思索,根据自下而上的原则来判断地话,先执行 decorator_a 再执行 decorator_b , 那么会先输出 Get in decotator_aGet in inner_a 再输出 Get in decotator_bGet in inner_b 。然而事实并非如此。

 2.2函数和函数调用的区别

为什么是先执行inner_b 再执行inner_a 呢?为了彻底看清上面的问题,得先分清两个概念:函数和函数调用。

上面的例子中 f 称之为函数, f(1) 称之为函数调用,后者是对前者传入参数进行求值的结果。在Python中函数也是一个对象,所以 函数f 是指代一个函数对象,它的值是函数本身, f(1) 是对函数的调用,它的值是调用的结果,这里的定义下 f(1) 的值为2。同样地,拿上面的decorator_a函数来说,它返回的是个函数对象inner_a ,这个函数对象是它内部定义的。在inner_a 里调用了函数 func ,将 函数func 的调用结果作为值返回。

2.3装饰器函数在被装饰函数定义好后立即执行

当装饰器装饰一个函数时,究竟发生了什么。现在简化我们的例子,假设是下面这样的:

def decorator_a(func):
    print ('Get in decorator_a')
    def inner_a(*args, **kwargs):
        print ('Get in inner_a')
        return func(*args, **kwargs)
    return inner_a

@decorator_a
def f(x):
    print ('Get in f')
    return x * 2

 解读:

@decorator_a  #等同于调用函数的时候上方加上:f=decorator_a(f)
def f(x):
    print ('Get in f')
    return x * 2

# 相当于

def f(x):
    print ('Get in f')
    return x * 2

f = decorator_a(f)

 运行分析:

当解释器执行这段代码时, decorator_a 已经调用了,它把函数名f 作为参数传递给形参fun, 返回它内部生成的一个函数inner_a,所以此后 f 指代的是 decorater_a 里面返回的 inner_a。所以当以后调用 函数f 时,实际上相当于调用 inner_a,传给函数 f 的参数会传给函数inner_a, 在调用函数inner_a 时,会把接收到的参数(函数名 f)传给inner_a里的 形参func=f  ,最后返回的是 调用函数f 的值,所以在最外面看起来就像是直接调用函数 f 一样。

2.4案例执行顺序分析

当理清上面两方面概念时,就可以清楚地看清最原始的例子中发生了什么。
当解释器执行下面这段代码时,实际上按照从下到上的顺序已经依次调用了 decorator_a和 decorator_b,这是会输出对应的 Get in decorator_a 和 Get in decorator_b。 这时候函数f 已经相当于 函数decorator_b里的 函数inner_b。但因为 函数 f 并没有被调用,所以 函数inner_b 并没有被调用,依次类推 函数inner_b内部的 函数inner_a也没有被调用,所以 Get in inner_a 和 Get in inner_b也不会被输出。

案例:

def decorator_a(func):
    print ('Get in decorator_a')
    def inner_a(*args, **kwargs):
        print ('Get in inner_a')
        return func(*args, **kwargs)
    return inner_a

def decorator_b(func):
    print ('Get in decorator_b')
    def inner_b(*args, **kwargs):
        print ('Get in inner_b')
        return func(*args, **kwargs)
    return inner_b

@decorator_b
@decorator_a
def f(x):
    print ('Get in f')
    return x * 2

f(1)

输出结果: 

Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f

然后第21行,当我们对函数  f 传入参数1进行调用时,函数inner_b被调用了,它会先打印 Get in inner_b,然后在函数 inner_b 内部调用了函数inner_a,所以会再打印 Get in inner_a, 然后在函数 inner_a 内部调用的原来的函数 f, 并且将结果作为最终的返回值。这时候你该知道为什么输出结果会是那样,以及对装饰器执行顺序实际发生了什么有一定了解了吧。

2.5案例执行顺序总结

-多个装饰器执行的顺序,是按照从下到上的顺序执行装饰器,再执行函数本身。

-装饰器的外函数和内函数之间的语句是没有装饰到目标函数上的,而是在装载装饰器时的附加操作。 

15~19行是装载装饰器的过程,相当于执行了f=decorator_b(decorator_a(f))

  1. 此时先执行decorator_a(f),结果是输出 Get in decorator_a,将形参func指向函数f、并返回函数inner_a;
  2. 然后执行函数 decorator_b(inner_a),结果是输出 Get in decorator_b,将形参 func指向函数inner_a、并返回函数inner_b
  3. 函数 f本身相当于函数inner_b

21行则是实际调用被装载的函数,用函数名 inner_b替代了函数名f

  1. 这时实际上执行的是函数inner_b;
  2. 运行到函数func()时执行函数inner_a;
  3. 再运行到函数 func()时执行未修饰的函数f

四、装饰器传参 

案例:传递2个参数

def w1(fn):
    """装饰器函数"""
    print("---正在装饰---")
    def inner(a, b):  # 如果a, b没有定义,那么会导致19行代码调用失败
        print("---正在验证---")
        fn(a, b)  # 如果没有把a, b当实参进行传递,那么会导致调用13行的函数失败
    return inner

@w1
def f1(a, b):
    """业务函数"""
    print(a + b)

f1(10, 20)

输出结果:

---正在装饰---
---正在验证---
30

案例:不定长参数

def w1(fn):
    """装饰器函数"""
    print("---正在装饰---")
    def inner(*args, **kwargs):  # 如果a, b没有定义,那么会导致19行代码调用失败
        print("---正在验证---")
        fn(*args, **kwargs)  # 如果没有把a, b当实参进行传递,那么会导致调用13行的函数失败
    return inner

@w1
def f1(a, b):
    """业务函数"""
    print(a + b)

@w1
def f2(a, b, c):
    """业务函数"""
    print(a + b + c)

f1(10, 20)
f2(10, 20, 30)

输出结果:

---正在装饰---
---正在装饰---
---正在验证---
30
---正在验证---
60

五、装饰器返回值 

案例 :

def w1(fn):
    """装饰器函数"""
    print("---正在装饰---")
    def inner():
        print("---正在验证---")
        ret = fn()  # 保存返回来的字符串
        return ret  # 把字符串返回到20行的调用处
    return inner

@w1
def test():
    """业务函数"""
    print("---test---")
    return "这是原函数返回值"

ret = test()  # 需要用参数来接收返回值
print(ret)

输出结果:

---正在装饰---
---正在验证---
---test---
这是原函数返回值

 六、通用装饰器

 案例:

def w1(fn):
    """装饰器函数"""
    def inner(*args, **kwargs):
        print("---记录日志---")
        ret = fn(*args, **kwargs)  # 保存返回来的字符串
        return ret  # 把字符串返回到20行的调用处
    return inner

@w1
def test1():
    """不带返回值"""
    print("---test1---")

@w1
def test2():
    """带返回值"""
    print("---test2---")
    return "这是原函数返回值"

@w1
def test3(a):
    """业务函数"""
    print("---test3中的数据:%d---" % a)

ret1 = test1()
print(ret1)
ret2 = test2()
print(ret2)
ret3 = test3(10)
print(ret3)

输出结果:

---记录日志---
---test1---
None
---记录日志---
---test2---
这是原函数返回值
---记录日志---
---test3中的数据:10---
None

 七、装饰器带参数

案例:

def func_arg(arg):
    def func(funtionName):
        def func_in():
            print("输出给装饰器传入的参数:%s" % arg)
            if arg == "hello":
                funtionName()
                funtionName()
            else:
                funtionName()
        return func_in
    return func

@func_arg("hello")
def test():
    print("---test---")

@func_arg("haha")
def test2():
    print("---test2---")

test()
test2()

 输出结果:

输出给装饰器传入的参数:hello
---test---
---test---
输出给装饰器传入的

 运行分析:

  1. 先执行func_arg("hello")函数,这个函数return的结果是func这个函数的引用;
  2. 执行@func;
  3. 使用@func对函数test进行装饰

猜你喜欢

转载自blog.csdn.net/weixin_44793743/article/details/126558156