当前位置: 首页 > news >正文

[Python]装饰器

在这里插入图片描述


前言

系列文章目录
[Python]目录
视频及资料和课件
链接:https://pan.baidu.com/s/1LCv_qyWslwB-MYw56fjbDg?pwd=1234
提取码:1234


文章目录

  • 前言
  • 1. 装饰器的定义
    • 1.1 装饰器的功能特点
  • 2. 装饰器的示例代码
  • 3. 装饰器的语法糖写法
  • 4. 装饰器的执行时机
  • 5. 装饰器的使用
    • 5.1 装饰器的使用场景
    • 5.2 装饰器实现已有函数执行时间的统计
  • 6. 通用装饰器的使用
    • 6.1 装饰带有参数的函数
    • 6.2 装饰带有参数和返回值的函数
    • 6.3 装饰带有不定长参数和返回值的函数
    • 6.4 通用装饰器
  • 7. 多个装饰器的使用
  • 8. 带有参数的装饰器
  • 9. 类装饰器的使用


1. 装饰器的定义

装饰器就是给已有函数增加额外功能函数,它本质上就是一个闭包函数

装饰器是一个闭包函数,也就是说装饰器是一个函数嵌套。

1.1 装饰器的功能特点

  1. 不修改已有函数的源代码
  2. 不修改已有函数的调用方式
  3. 给已有函数增加额外的功能

2. 装饰器的示例代码

需要实现一个功能:
在发表评论之前,添加一个"已进行登录验证"

# 需要实现一个功能:
# 在发表评论之前,添加一个"已进行登录验证"

# 定义装饰器
# 如果闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数称为装饰器
def decorator(func):
    def inner():
        # 在内部函数里面对已有的函数进行装饰
        print('已进行登录验证...')
        func()

    # 返回内部函数
    return inner


# 先定义一个函数
def comment():
    print('发表评论...')


# 调用装饰器对已有函数进行装饰, comment = inner
comment = decorator(comment)
# 调用定义的函数
comment()

在这里插入图片描述

代码说明:
闭包函数有且只有一个参数,并且该参数是函数类型,这样定义的函数才是装饰器
写代码要遵循开放封闭原则,它规定已经实现的功能代码不允许被修改,但可以被扩展。

装饰器相当于原来的函数由闭包的外部函数进行保存,因为闭包内使用了外部函数的变量,且闭包函数没有进行释放,所以外部函数的变量无法进行释放,然后将原来声明的函数进行指向的改变,原来声明的函数指向返回的闭包。

使用装饰器对函数进行功能的拓展,其实是改变函数的指向,由闭包去调用原函数。

3. 装饰器的语法糖写法

如果有多个函数都需要添加登录验证的功能,每次都需要编写comment = decorator(comment)这样代码对已有函数进行装饰,这种做法还是比较麻烦。

Python给提供了一个装饰函数更加简单的写法,那就是语法糖。

语法糖的书写格式是:

@装饰器名字

通过语法糖的方式也可以完成对已有函数的装饰.

# 需要实现一个功能:
# 在发表评论之前,添加一个"已进行登录验证"

# 定义装饰器
# 如果闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数称为装饰器
def decorator(func):
    def inner():
        # 在内部函数里面对已有的函数进行装饰
        print('已进行登录验证...')
        func()

    # 返回内部函数
    return inner


# 先定义一个函数
# 使用语法糖的方法对 comment 函数进行装饰
# 此代码相当于 comment = decorator(comment)
# 对函数进行装饰后,comment = inner
@decorator
def comment():
    print('发表评论...')


# 调用定义的函数
comment()

在这里插入图片描述

4. 装饰器的执行时机

装饰器的执行时机:
当当前模块加载完成后,装饰器会立即执行,对已有的函数进行装饰。

# 需要实现一个功能:
# 在发表评论之前,添加一个"已进行登录验证"

# 定义装饰器
# 如果闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数称为装饰器
def decorator(func):
    print('装饰器执行了...')
    def inner():
        # 在内部函数里面对已有的函数进行装饰
        print('已进行登录验证...')
        func()

    # 返回内部函数
    return inner


# 先定义一个函数
# 使用语法糖的方法对 comment 函数进行装饰
# 此代码相当于 comment = decorator(comment)
# 对函数进行装饰后,comment = inner
@decorator
def comment():
    print('发表评论...')


# 调用定义的函数
# comment()

导入装饰器的模块

import pycode

在这里插入图片描述

上述代码说明:当当前模块加载完成后,装饰器会立即执行,对已有的函数进行装饰。

5. 装饰器的使用

5.1 装饰器的使用场景

  1. 对函数的功能进行拓展
  2. 函数执行时间的统计
  3. 输出日志信息

5.2 装饰器实现已有函数执行时间的统计

import time


# 定义装饰器
def decorator(func):
    def inner():
        # 获取函数执行开始的时间
        begin = time.time()
        # 调用执行函数
        func()
        # 获取函数执行结束的时间
        end = time.time()
        # 打印函数的执行时间
        print('函数执行完成,用时:', end-begin)

    # 返回内部函数
    return inner


# 定义函数,并对其进行装饰
# work = decorator(work)
@decorator
def work():
    sum = 0
    for i in range(10000):
        sum += i

    print(sum)


# 调用执行函数
work()

在这里插入图片描述

6. 通用装饰器的使用

通用装饰器,即这个装饰器可以装饰任意的函数

6.1 装饰带有参数的函数

使用装饰器装饰带有参数的函数时,装饰器的内部函数的类型需要和要进行装饰的已有函数的类型保持一致,即内部函数需要的参数个数要和需要进行装饰的函数一致。

# 定义装饰器
def decorator(func):
    # 由于内部函数内执行的函数需要两个参数,所以这里需要接收两个参数
    # 内部函数的类型要和需要进行装饰的已有的函数的类型一致
    def inner(num1, num2):
        # 在内部函数对已有函数进行装饰
        print('正在执行函数...')
        func(num1, num2)

    return inner

# 进行装饰
@decorator
def add(num1, num2):
    print('相加结果为:', num1+num2)


add(1, 2)

在这里插入图片描述

6.2 装饰带有参数和返回值的函数

使用装饰器装饰函数时,装饰器的内部函数的类型需要和要进行装饰的已有函数的类型保持一致。

装饰带有返回值的函数,装饰器的内部函数需要接收调用函数的返回值,与此同时并向外返回接收到的返回值。

# 定义装饰器
def decorator(func):
    # 由于内部函数内执行的函数需要两个参数,所以这里需要接收两个参数
    # 内部函数的类型要和需要进行装饰的已有的函数的类型一致
    def inner(num1, num2):
        # 在内部函数对已有函数进行装饰
        print('正在执行函数...')
        res = func(num1, num2)
        return res

    return inner


# 进行装饰
@decorator
def add(num1, num2):
    # print('相加结果为:', num1+num2)
    return num1 + num2


res = add(1, 2)
print('相加的结果为:', res)

在这里插入图片描述

6.3 装饰带有不定长参数和返回值的函数

  • *args:接收参数后,为元组类型
  • **kwargs:接收参数后,为字典类型

该装饰器就是通用装饰器。

# 定义装饰器
def decorator(func):
    # 由于内部函数调用的函数接收的不定长
    # 所以此处接收的参数也为不定长
    def inner(*args, **kwargs):
        # 在内部函数对已有函数进行装饰
        print('正在执行函数...')
        # 注意,在次数传参时,不能直接将 args, kwargs 直接传入
        # 直接传入 args, kwargs 会被作为两个单独的参数,一个元组类型,一个字典类型
        # 所以这里需要先进行拆包
        # 这里对元组和字典进行拆包,仅限于结合不定长参数的函数使用
        res = func(*args, **kwargs)
        return res

    return inner


# 该函数接收不定长参数,不定长关键字参数
# 进行装饰
@decorator
def add(*args, **kwargs):
    sum = 0
    # 循环遍历 args,看是否有不定长参数传入函数
    for val in args:
        sum += val

    # 循环遍历 kwargs,看是否有不定长关键字参数传入函数
    # 由于次数不需要 键 ,所以只遍历 值
    for val in kwargs.values():
        sum += val

    return sum


res = add(1, 2)
print('相加的结果为:', res)

在这里插入图片描述

装饰什么样的函数,内部函数就什么样。

6.4 通用装饰器

# 定义装饰器
def decorator(func):
    # 由于内部函数调用的函数接收的不定长
    # 所以此处接收的参数也为不定长
    def inner(*args, **kwargs):
        # 在内部函数对已有函数进行装饰
        print('正在执行函数...')
        # 注意,在次数传参时,不能直接将 args, kwargs 直接传入
        # 直接传入 args, kwargs 会被作为两个单独的参数,一个元组类型,一个字典类型
        # 所以这里需要先进行拆包
        # 这里对元组和字典进行拆包,仅限于结合不定长参数的函数使用
        res = func(*args, **kwargs)
        # 进行装饰的函数没有返回值,这里接受和返回都为 None
        return res

    return inner


@decorator
def f1():
    print('测试通用装饰器')


@decorator
def f2(num):
    return num


f1()
res = f2(2)
print(res)

在这里插入图片描述

7. 多个装饰器的使用

使用多个装饰器装饰一个函数。

需要实现的功能:
在原函数输出一句话的基础上,在该句话前后加上<p></p>,在此基础上,再在前后加上<div></div>

# 装饰器 1
def make_div(func):
    print('make_div执行了...')

    def inner():
        # 对内部函数进行装饰
        res = '<div>' + func() + '</div>'
        return res

    return inner


# 装饰器 2
def make_p(func):
    print('make_p执行了...')

    def inner():
        # 对内部函数进行装饰
        res = '<p>' + func() + '</p>'
        return res

    return inner


# 装饰执行过程:make_div( make_p( content ) )
@make_div
@make_p
def content():
    return '人生苦短,我用Python'


res = content()
print(res)

在这里插入图片描述

从执行结果,可以看出,装饰的执行过程是从下向上进行装饰的
在这里插入图片描述
在这里插入图片描述

8. 带有参数的装饰器

带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,对不同的函数,传入不同的参数,可以进行不同的装饰。

语法格式:

@装饰器(参数,...)

注意:装饰器只能接收一个参数,且这个参数是函数类型

# 创建一个函数,用于创建并返回装饰器
# 该函数接收的参数为 对函数不同的装饰参数
def make_decorator(dec):
    # 定义装饰器
    def decorator(func):
        def inner(a, b):
            # 对函数进行装饰
            print(dec)
            res = func(a, b)
            return res

        return inner

    # 返回创建的装饰器
    return decorator


# 定义函数,并对该函数进行装饰
# 装饰函数的过程:
# 1. 先执行函数 make_decorator() 得到装饰器 decorator
# 2. 使用得到的装饰器 decorator 装饰函数 decorator(add_num) 返回装饰后的函数
@make_decorator('正在进行加法计算...')
def add_num(a, b):
    return a + b


# 调用函数
res = add_num(1, 2)
print('计算结果为: ', res)

在这里插入图片描述

9. 类装饰器的使用

使用类来装饰函数

使用类装饰器装饰函数,需要实现__call__方法,表示对象是一个可调用对象,可以像调用函数一样进行调用。

# 定义类装饰器
class MyDecorator(object):
    # 当使用类装饰器装饰函数,默认会调用 __init__ 方法
    # 使用 __init__ 初始化对象
    def __init__(self, func):
        # 由于对函数进行装饰后,装饰前的函数不希望再次被调用
        # 所以原函数设置为私有属性
        self.__func = func

    # 实现`__call__`方法,表示对象是一个可调用对象,可以像调用函数一样进行调用。
    def __call__(self, *args, **kwargs):
        # 装饰函数
        print('课讲完了')
        self.__func()


# 使用类装饰器,此处相当于
# show = MyDecorator(show)
# 所以装饰以后为一个对象,对象能够调用需要实现 __call__ 方法
@MyDecorator
def show():
    print('下课了...')


show()

在这里插入图片描述

拓展:函数之所以能够调用,是因为函数内部实现了 __call__ 方法。

def test():
    print('test()')


print(dir(test))

在这里插入图片描述

相关文章:

  • Linux入门之使用 arp 管理ARP协议缓存
  • 1063:最大跨度值
  • 六、【计算】大数据Shuffle原理与实践(下) | 青训营笔记
  • C语言程序——Switch分支选择程序
  • SpringBoot+Vue项目大学生心理服务系统
  • go语言使用grpc
  • 研究生英语单词学习——Learning English
  • 【Java设计模式 规范与重构】 六 代码重构小结
  • ShardingSphere水平分片、多表关联、绑定表、广播表
  • SSM框架速成2——Spring5速成总结
  • 力扣第312场周赛题解:
  • MySQL流程控制函数
  • GB/T28181-2016基于RTP的视音频数据封装和技术实现
  • String类的详解
  • C/C++新手看过来----新手问题汇总分析
  • 2017-09-12 前端日报
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • JWT究竟是什么呢?
  • MySQL-事务管理(基础)
  • Redis字符串类型内部编码剖析
  • Vim 折腾记
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 看域名解析域名安全对SEO的影响
  • 数组的操作
  • 微信开源mars源码分析1—上层samples分析
  • 译自由幺半群
  • 源码安装memcached和php memcache扩展
  • zabbix3.2监控linux磁盘IO
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • 数据库巡检项
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (心得)获取一个数二进制序列中所有的偶数位和奇数位, 分别输出二进制序列。
  • (一)RocketMQ初步认识
  • (一)Thymeleaf用法——Thymeleaf简介
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .NET delegate 委托 、 Event 事件
  • .NET是什么
  • .NET委托:一个关于C#的睡前故事
  • .NET下ASPX编程的几个小问题
  • .secret勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • @Transactional注解下,循环取序列的值,但得到的值都相同的问题
  • [AIGC 大数据基础]hive浅谈
  • [BZOJ1178][Apio2009]CONVENTION会议中心
  • [BZOJ3757] 苹果树
  • [C# 网络编程系列]专题六:UDP编程
  • [C#]C#学习笔记-CIL和动态程序集
  • [C++]四种方式求解最大子序列求和问题
  • [CareerCup] 6.1 Find Heavy Bottle 寻找重瓶子