IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> 终于搞明白了Python装饰器,我来说说我理解的装饰器 -> 正文阅读

[Python知识库]终于搞明白了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_a,?Get in inner_a?再输出?Get in decotator_b,?Get 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进行装饰

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 01:07:23  更:2022-09-04 01:09:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 10:49:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码