020.Python基础进阶_装饰器

无奋斗不青春

我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈
入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈
虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈
PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)文章合集 👈👈
Oracle数据库教程:👉👉 Oracle数据库文章合集 👈👈
优 质 资 源 下 载 :👉👉 资源下载合集 👈👈

分隔线

前言

  • 装饰器(Decorator)是Python中一种特殊的语法, 是一种强大的函数或类修饰机制,用于在不修改原始函数或类代码的情况下,对其进行功能扩展或修改。
  • 装饰器基于函数式编程的概念,通过将函数作为参数传递给另一个函数,并返回一个新的函数来实现

作用

  • 装饰器是在不修改原始函数的函数名以及函数的前提下,给原始函数添加额外的功能或修改其行为

特点

  • 装饰器的本质就是闭包,它需要把一个callable对象(能够打括号调用的对象)作为参数转递进来
  • 装饰器是一个函数,它接受一个函数作为参数输入,并返回一个新的函数作为输出,这个新函数是内嵌原函数的函数
  • python中的装饰器(decorator)一般采用语法糖的形式,是一种语法格式。比如:@classmethod,@staticmethod,@property,@wraps(),等都是python中的装饰器

“开放-封闭”原则

  • 程序功能扩展时,不能违反软件开发中的一个原则:“开放-封闭”原则
    • 开放-封闭 原则规定已经实现的功能代码不允许被修改,但可以被扩展,即:
    • 封闭:已实现的功能代码块不允许被修改
    • 开放:对现有功能的扩展开放

案例解析

  • 基本需求:开发两个功能:发朋友圈和发消息。用户点击按钮1发朋友圈,用户点击按钮2发消息

    # 功能代码,定义两个功能函数
    def send_pyq():
        print('用户点击了按钮1,执行发朋友圈动作....')
    
    
    def send_xx():
        print('用户点击了按钮2,执行发消息动作....')
    
    
    # 业务逻辑代码
    
    btn_index = input('用户按下了按钮(1, 2):')     # 模拟用户按下按钮
    
    # 根据用户按下按钮,分别执行不同功能代码
    if btn_index == '1':
        send_pyq()
    elif btn_index == '2':
        send_xx()
    
    
    • 上面的代码已经满足了基本需求,但是甲方爸爸发现这个好像所有人都可以操作,完全没管控…所以提出了新的需求!
  • 需求升级:在用户进行发朋友圈和发消息之前对用户登录进行验证,已登录则可以操作,否则退出系统

    • 解决思路1:在业务逻辑代码中添加判断,用户点击了按钮后,判断是否登录,登录了则执行功能函数,没登录则不执行…
    # 功能代码,定义两个功能函数
    def send_pyq():
        print('用户点击了按钮1,执行发朋友圈动作....')
    
    
    def send_xx():
        print('用户点击了按钮2,执行发消息动作....')
    
    
    # 业务逻辑代码
    
    btn_index = input('用户按下了按钮(1, 2):')     # 模拟用户按下按钮
    
    # 根据用户按下按钮,分别执行不同功能代码
    if btn_index == '1':
        login_status = input('获取用户登录状态:')   # 模拟获取用户登录状态
        if login_status == '已登录':
            send_pyq()
        else:
            exit()
    elif btn_index == '2':
        login_status = input('获取用户登录状态:')  # 模拟获取用户登录状态
        if login_status == '已登录':
            send_xx()
        else:
            exit()
    

    这一段代码看上去很完美的解决了验证问题。but…这个里面有几个问题点:

    • 1、 大家有没有想过,在一个系统程序中,可能有N多个界面可以进行发朋友圈和发消息的操作,难道我们要把所有界面的都撸一遍?把这个判断逻辑复制,全部粘贴一遍?
    • 2、在实际开发的过程中,一个验证的过程可能有十几行甚至几十行代码,我们这样复制粘贴到各个地方,就会出现代码冗余并且不可重用的问题
  • 解决方法

    • 1、不可重用:定义一个函数,将整个验证过程封装起来
    • 2、代码冗余:我们将这个验证函数写到功能函数内部
    # 功能代码,定义两个功能函数
    def send_pyq():
        status = login_check()
        if status:
            print('用户点击了按钮1,执行发朋友圈动作....')
        else:
            exit()
    
    
    def send_xx():
        status = login_check()
        if status:
            print('用户点击了按钮2,执行发消息动作....')
        else:
            exit()
    
    
    def login_check():
        login_status = input('获取用户登录状态:')   # 模拟获取用户登录状态
        if login_status == '已登录':
            return True
        else:
            return False
    
    
    # 业务逻辑代码
    
    btn_index = input('用户按下了按钮(1, 2):')     # 模拟用户按下按钮
    
    # 根据用户按下按钮,分别执行不同功能代码
    if btn_index == '1':
        send_pyq()
    
    elif btn_index == '2':
        send_xx()
    
    • 这样看上去好像解决了上面两个问题。但是这样写又出现了新问题…
    • 1、这样写的代码违反了功能函数单一性原则(在发朋友圈和发消息的功能函数中,既做了登录验证,又做了发朋友圈/发消息的功能)
    • 2、违反了软件开发中的“开放-封闭”原则,对已经实现的功能代码进行了修改
  • 解决方法

    • 为了解决上面的问题,可以考虑将登录验证单独封装成函数,然后将发朋友圈和发消息功能函数传入登录验证函数当中
    # 功能代码,定义两个功能函数
    def send_pyq():
        print('用户点击了按钮1,执行发朋友圈动作....')
    
    
    def send_xx():
        print('用户点击了按钮2,执行发消息动作....')
    
    
    def login_check(func):
        login_status = input('获取用户登录状态:')  # 模拟获取用户登录状态
        if login_status == '已登录':
            print('用户已登录.....')
            func()
        else:
            print('用户未登录,退出系统...')
            exit()
    
    
    # 业务逻辑代码
    
    btn_index = input('用户按下了按钮(1, 2):')  # 模拟用户按下按钮
    
    # 根据用户按下按钮,分别执行不同功能代码
    if btn_index == '1':
        login_check(send_pyq)
    
    elif btn_index == '2':
        login_check(send_xx)    
    
    
    • 上面的代码看上去已经解决了代码冗余的问题,但是又有了新的问题
    • 1、这种写法需要去修改业务逻辑代码中的调用方法,如果用到该功能的界面比较多的话,那要修改的地方就很多,所以…
  • 解决方法

    • 在解决这个问题之前,我们需要了解一个知识点:
      • 函数名的实质也就是一个变量名
      • 变量也就可以被重新赋值
    • 示例
      def test1():
          print('test1函数内部打印....')
      
      
      def test2(func):
          def inner():
              print('test2函数内部打印....')
              func()
          return inner
          
      
      test1 = test2(test1)        # 重新给test1赋值,此时test1 被赋值为 inner函数对象 test1 = inner
      
      test1()                     # test1() 就相当于 inner() 
      
      # -----输出结果-----
      test2函数内部打印....
      test1函数内部打印....
          
      
      • 在这个示例代码中,我们用到了函数的闭包技巧
    • 根据上面这个示例代码的思路,我们可以将我们的代码修改为
    # 功能代码,定义两个功能函数
    def send_pyq():
        print('用户点击了按钮1,执行发朋友圈动作....')
    
    
    def send_xx():
        print('用户点击了按钮2,执行发消息动作....')
    
    
    def login_check(func):
        def inner():
            login_status = input('获取用户登录状态:')  # 模拟获取用户登录状态
            if login_status == '已登录':
                print('用户已登录.....')
                func()
            else:
                print('用户未登录,退出系统...')
                exit()
        return inner
    
    
    send_pyq = login_check(send_pyq)
    send_xx = login_check(send_xx)
    
    
    # 业务逻辑代码
    
    btn_index = input('用户按下了按钮(1, 2):')  # 模拟用户按下按钮
    
    # 根据用户按下按钮,分别执行不同功能代码
    if btn_index == '1':
        send_pyq()
    
    elif btn_index == '2':
        send_xx()    
    
    
    • 从上面代码可以看到,我们既避免了代码冗余,又做到了登录验证过程的重用性,而且不需要去修改业务逻辑代码里面的调用方法
  • 上面的代码,我们需要通过send_pyq = login_check(send_pyq)这种方式对函数进行重新赋值,写起来比较麻烦,而且不是那么好看

  • 那么,python给我们提供了一种语法糖(使用 @ 语法符,在函数定义之前增加装饰器函数的名称)

  • 语法

    @decorator_func
    def my_func():
        pass
    
  • 我们使用语法糖的方式对我们的代码进行修改

    # 功能代码,定义两个功能函数
    def login_check(func):
        def inner():
            login_status = input('获取用户登录状态:')  # 模拟获取用户登录状态
            if login_status == '已登录':
                print('用户已登录.....')
                func()
            else:
                print('用户未登录,退出系统...')
                exit()
    
        return inner
    
    
    @login_check
    def send_pyq():
        print('用户点击了按钮1,执行发朋友圈动作....')
    
    
    @login_check
    def send_xx():
        print('用户点击了按钮2,执行发消息动作....')
    
    # send_pyq = login_check(send_pyq)
    # send_xx = login_check(send_xx)
    
    
    # 业务逻辑代码
    btn_index = input('用户按下了按钮(1, 2):')  # 模拟用户按下按钮
    
    # 根据用户按下按钮,分别执行不同功能代码
    if btn_index == '1':
        send_pyq()
    
    elif btn_index == '2':
        send_xx()
    

装饰器的执行时间

  • 当程序运行到@装饰器函数名这一句的时候,装饰器函数就会立即执行,并不是等到调用原函数的时候才执行
    def check(func):
        print('check装饰器函数')
    
        def inner():
            print('验证操作....')
            func()
        return inner
    
    
    @check
    def send_pyq():
        print('进行发朋友圈操作')
    
    
    # -----输出结果-----
    check装饰器函数
    
    
  • 上面这段代码中,我们并没有调用原始函数send_pyq(),但是从输出结果可以看到装饰器函数check(func)其实是已经执行了

装饰器执行图解

在这里插入图片描述


装饰器叠加

  • 多个装饰器叠加:从上到下装饰,代码编译顺序:从下到上编译,调用执行顺序:从上到下输出
    # 示例1
    
    def decorator_outer(func):
        print('外层装饰器,decorator_outer')
    
        def outer_inner():
            print('外层装饰器内部函数, outer_inner')
            func()
    
        return outer_inner
    
    
    def decorator_subcoat(func):
        print('内层装饰器,decorator_subcoat')
    
        def subcoat_inner():
            print('内层装饰器内部函数, subcoat_inner')
            func()
    
        return subcoat_inner
    
    
    @decorator_outer
    @decorator_subcoat
    def send_pyq():
        print('进行发朋友圈操作')
    
    
    print('==========分隔线==========')
    
    send_pyq()
    
    # -----输出结果-----
    内层装饰器,decorator_subcoat
    外层装饰器,decorator_outer
    ==========分隔线==========
    外层装饰器内部函数, outer_inner
    内层装饰器内部函数, subcoat_inner
    进行发朋友圈操作
    
    
    # =====================================
    
    
    # 示例2
    
    def decorator_outer(func):
        print('外层装饰器,decorator_outer')
    
        def outer_inner():
            print('外层装饰器内部函数, outer_inner')
            func()
    
        return outer_inner
    
    
    def decorator_subcoat(func):
        print('内层装饰器,decorator_subcoat')
    
        def subcoat_inner():
            print('内层装饰器内部函数, subcoat_inner')
            func()
    
        return subcoat_inner
    
    
    @decorator_subcoat
    @decorator_outer
    def send_pyq():
        print('进行发朋友圈操作')
    
    
    print('==========分隔线==========')
    
    send_pyq()
    
    # -----输出结果-----
    外层装饰器,decorator_outer
    内层装饰器,decorator_subcoat
    ==========分隔线==========
    内层装饰器内部函数, subcoat_inner
    外层装饰器内部函数, outer_inner
    进行发朋友圈操作
    

装饰器的应用

简单的装饰器

  • 简单的装饰器,装饰函数只需要两层嵌套即可
    def check(func):
        def inner():
            print('验证操作....')
            func()
        return inner
    
    
    @check
    def send_pyq():
        print('进行发朋友圈操作')
    

装饰器装饰带参数的函数

  • 装饰带参数的函数,装饰函数嵌套的函数也需要带参数
  • 通过*args**kwargs 接收不定长参数
    def check(func):
        def inner(*args, **kwargs):
            print('登录验证操作...')
            func(*args, **kwargs)          # 此时func指向 原send_pyq函数的内存地址,所以需要接收参数
        return inner
    
    
    @check
    def send_pyq(content):
        print(f'进行发朋友圈操作:{content}')
    
    @check
    def send_xx(username, content):
        print(f'向{username}发送消息:{content}')
    
    
    send_pyq('朋友圈内容')      # 通过装饰器之后,send_pyq指向inner函数的内存地址
    send_xx('张三', '今天放假')
    
    

装饰器装饰有返回值的函数

  • 装饰器的内嵌函数格式要保持与原函数的格式一致
    def check(func):
        def inner(*args, **kwargs):
            print('登录验证操作...')
            res = func(*args, **kwargs)         # res接收原函数执行的返回值
            return res                          # 将res接收到的返回值,再作为inner函数的返回值
        return inner
    
    
    @check
    def send_pyq(content):
        print(f'进行发朋友圈操作:{content}')
        return '发朋友圈成功'
    
    
    @check
    def send_xx(username, content):
        print(f'向{username}发送消息:{content}')
    
    
    re1 = send_pyq('文案内容')                  # 此时re1接收到的是inner函数的返回值,其实也就是原函数的返回值
    re2 = send_xx('张三', '今天放假')
    
    print(re1)
    print(re2)
    

带参数的装饰器

  • 通过@装饰器(参数) 的方式,调用这个函数,并传递参数;并把返回值再次当做装饰器进行使用
  • 先计算 @ 后面的内容,把这个内容的计算结果(返回值)当做一个装饰器
    # 示例1:装饰器不带参数,要修改打印内容之前打印的分隔线很麻烦
    def zsq(func):
        def inner(*args, **kwargs):
            print('-' * 30)
            func(*args, **kwargs)
        return inner
    
    
    @zsq
    def test(content):
        print(content)
    
    
    test('打印内容')
    
  • 示例1中,我们装饰器已经写好了,但是现在客户需要在打印内容之前不是打印-了,想打印*…那我们就要去修改装饰器。
  • 但是,如果另一个用户又想在打印内容之前打印一行=怎么办呢?
  • 此时,我们就需要通过给装饰器添加参数来解决了…
    def get_zsq(char):
        def zsq(func):
            def inner(*args, **kwargs):
                print(char * 30)
                func(*args, **kwargs)
    
            return inner
    
        return zsq
    
    
    @get_zsq('-')
    def test1(content):
        print(content)
    
    
    @get_zsq('*')
    def test2(content):
        print(content)
    
    
    
    test1('打印内容1')
    test2('打印内容2')
    
    # -----输出结果-----
    ------------------------------
    打印内容1
    ******************************
    打印内容2
    
    
  • 图解
    def get_zsq(char):
        def zsq(func):
            def inner(*args, **kwargs):
                print(char * 30)
                func(*args, **kwargs)
    
            return inner
    
        return zsq
    
    
    @get_zsq('*')
    def test(content):
        print(content)
    
    
    test('打印内容')
    
    • 在这里插入图片描述

个人理解

  • 语法糖会将 @ 符号后面当做一个整体,默认增加一个 (原函数对象) 的参数
  • 示例1: 不带参数装饰器
    def get_zsq(char):
        print('char参数值:', char)
        
        def inner(*args, **kwargs):
            print(char * 30)
            func(*args, **kwargs)
        return inner
    
    
    @get_zsq                    # ---> 相当于 @get_zsq(test)
    def test(content):
        print(content)
    
    
    # -----执行结果----
    char参数值: <function test at 0x000001E92B735310>
    
  • 示例2: 带参数装饰器
    def get_zsq(char):
        print('char参数值:', char)
        def zsq(func):
            def inner(*args, **kwargs):
                print(char * 30)
                func(*args, **kwargs)
    
            return inner
    
        return zsq
    
    
    @get_zsq('装饰器参数')      # ---> 相当于 @get_zsq('装饰器参数')(test)
    def test(content):
        print(content)
    
    
    # -----执行结果----
    char参数值: 装饰器参数
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失心疯_2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值