美文网首页
Python基础(十二): 装饰器

Python基础(十二): 装饰器

作者: 伯wen | 来源:发表于2018-03-04 01:10 被阅读7次

    装饰器使用的是函数中的闭包功能

    一、装饰器

    装饰器: 在函数名以及函数体不改变的前提下, 给一个函数附加一些额外代码

    二、语法

    • 语法
    @装饰器
    def 被装饰的函数():
        code
    

    三、举例

    def name(func):
        def inner():
            print("zhangsan")
            func()
        return inner
    
    @name
    def say():
        print("hello world")
    
    say()
    
    # 打印结果: 
    zhangsan
    hellow world
    

    四、讲解

    • say函数定义上添加@name 等价于 say = name(say)
    @name
    def say():
        print("hello world")
    
    • 等价于:
    def say():
        print("hello world")
    say = name(say)
    

    五、注意: 装饰器的执行时间是立即实行

    • 当代码执行到@name时, 实际上函数name已经被执行了, 并且传入的参数func的值是say, 执行后say指向新的函数, 此时变成下面的代码:
    def inner()
        print("zhangsan")
        say()
    
    • 结果: say没有被调用, 依然打印 xxxxxxx
    def name(func):
        print("xxxxxxx")
        def inner():
            print("zhangsan")
            func()
        return inner
    
    @name
    def say():
        print("hello world")
    
    # 打印结果: 
    xxxxxxx
    

    六、案例: 添加装饰器的过程

    现有模拟案例如下:

    1. 已知有两个功能函数, 分别是发图片和发说说

      def ftp():
          print("发图片")
      
      def fss():
          print("发说说")
      
    2. 在页面上有两个按钮, 点击第一个按钮时发出片, 点击第二个按钮时发说说

      btnIndex = 1
      if btnIndex == 1:
          ftp()
      else:
          fss()
      

      注意: 此处是模拟, 当btnIndex的值为1时, 认为点击第一个按钮, 值为2时, 认为点击第二个按钮

    3. 由于发图片和发说说都是用户登陆后才能操作, 所以在发图片和发说说前, 都需要进行登录验证

      btnIndex = 1
      if btnIndex == 1:
          print("登录验证")
          ftp()
      else:
          print("登录验证")
          fss()
      

      注意: 此处使用print("登录验证")表示了登录验证的功能, 真实项目中替换为具体的代码实现即可

    4. 上面已经解决了在发图片发说说前的登录验证功能

      但是却有一个问题, 那就是发图片和发说说这两个功能, 也可以在当前项目中其他的地方使用

      而那时又需要做登录验证操作

      这样会造成代码冗余, 且不利于维护, 不利于阅读

      所以我们将登录验证功能换一个地方, 而逻辑代码不变, 如下

      def ftp():
          print("登录验证")
          print("发图片")
      
      def fss():
          print("登录验证")
          print("发说说")
          
      btnIndex = 1
      if btnIndex == 1:
          ftp()
      else:
          fss()
      
    5. 第4步中解决了在使用发图片和发说说两个功能时, 只需要直接调用函数即可, 函数内部已经实现了对登录的验证操作, 多个地方使用这两个功能也只需要直接调用即可

      但是, 这里有一个问题, 我们的模拟代码print("登录验证")只有一句话, 而在实际操作过程中, 一个登录验证可能会有很多行的代码, 而发说说和发图片中都使用了这段代码, 此时会造成代码冗余

      所以, 在第四步的基础上, 将登录验证的代码抽取出来, 此时有如下代码

      def checkLogin():
          print("登录验证")
      
      def ftp():
          checkLogin()
          print("发图片")
      
      def fss():
          checkLogin()
          print("发说说")
      

      我们只需要在发图片和发说说的具体实现前, 加入登录验证函数的调用即可

    6. 在第5步中, 我们解决了登录验证代码过多, 可能导致第四步中, 发图片函数和发说说函数中登录验证代码过多的冗余问题

      看起来问题解决了, 但是却违背了"开放封闭"原则:

       1, 已经写好的代码, 尽可能不要修改
       2, 如果想要新增功能, 在原先代码基础上, 单独进行扩展
      

      并且违背了功能模块, 功能单一职责

      所以我们需要将发图片和发说说两个函数中的checkLogin(), 去掉, 并实现下面的函数

      def ftp():
          print("发图片")
      
      def fss():
          print("发说说")
      
      def checkLoginToFtp():
          print("登录验证")
          ftp()
          
      def checkLoginToFss():
          print("登录验证")
          fss()
      

      在业务逻辑中, 当按钮被点击时的调用如下

      btnIndex = 1
      if btnIndex == 1:
          checkLoginToFtp()
      else:
          checkLoginToFss()
      

      这样, 我们就在不改变ftp()fss()两个函数的基础上, 添加了登录验证功能

    7. 第6步中的代码会出现冗余现象, 比如checkLoginToFtpcheckLoginToFss都调用了print("登录验证")

      这里对第6步中的代码进行简化

      def ftp():
          print("发图片")
      
      def fss():
          print("发说说")
      
      def checkLogin(func):
          print("登录验证")
          func()
      

      调用时

      btnIndex = 1
      if btnIndex == 1:
          checkLogin(ftp)
      else:
          checkLogin(fss)
      

      此时, 可以直接使用checkLogin函数, 对在指定函数前添加登录验证了

    8. 第7步中, 对代码进行了优化, 可以自由的在任何函数前都添加登录验证操作, 且不会出现代码冗余的情况

      但是, 这里修改了第2步中, 逻辑代码部分的实现内容

      现在要做的是, 在第1步和第2步中的代码都不变的基础上, 通过添加额外的代码, 实现登录验证的操作

      已知: 两个功能函数 ftpfss, 而这两个函数的函数名, 实质上就是变量, 那么我们通过将函数名重新赋值的方式, 修改函数指向

      def checkLogin(func):
          print("登录验证")
          func()
          
      ftp = checkLogin(ftp)
      fss = checkLogin(fss)
      

      此时, ftpfss的值已经不是之前的函数, 而是checkLogin(ftp)checkLogin(fss)的返回值, 即 None

    9. 在第8步中, 我们修改了变量ftpfss的指向, 此时两个变量的值都是 None

      我们知道, 第2步中的ftpfss是两个不同的函数, 所以才能被调用, 而此时的ftpfss已经无法像函数一样被调用

      我们需要修改checkLogin函数, 让他的返回值依然是函数

      def checkLogin(func):
          def inner():
              print("登录验证")
              func()
          return inner
      

      这样, 我们在给ftpfss赋值

      ftp = checkLogin(ftp)
      fss = checkLogin(fss)
      

      此时,

      ftp的值是函数

      def inner():
          print("登录验证")
          ftp()
      

      fss的值是函数

      def inner():
          print("登录验证")
          fss()
      

      此时ftpfss可以如下调用

      btnIndex = 1
      if btnIndex == 1:
          ftp()
      else:
          fss()
      
    10. 我们看看现在的代码

      def checkLogin(func):
          def inner():
              print("登录验证")
              func()
          return inner
          
      def ftp():
          print("发图片")
      
      def fss():
          print("发说说")
          
      ftp = checkLogin(ftp)
      fss = checkLogin(fss)
      
      btnIndex = 1
      if btnIndex == 1:
          ftp()
      else:
          fss()
      

      此时, 通过加入的代码, 已经将ftpfss两个函数指向了新的函数

      而这个新函数在实现原有代码之前, 加入了登录验证功能

      此时: 我们实现了, 在1和2代码不变的情况下, 加入新代码, 增加了新的功能

    11. 在上述代码中, 有ftp = checkLogin(ftp)fss = checkLogin(fss)两个重新赋值的操作, 在Python中, 有一个简单写法, 与这两句等价

      def checkLogin(func):
          def inner():
              print("登录验证")
              func()
          return inner
          
      @checkLogin
      def ftp():
          print("发图片")
          
      @checkLogin
      def fss():
          print("发说说")
      

      这种简易方法, 就是语法糖, 即能用简单的写法, 写出复杂的功能, 如同糖一样甜

      上面的代码中ftp函数上添加@checkLogin 等价于 ftp = checkLogin(ftp)

    12. 第11步中的 checkLogin函数实现@checkLogin, 就是装饰器

      装饰器: 在函数名以及函数体不改变的前提下, 给一个函数附加一些额外代码

    总结: 通过闭包的形式, 将原有函数名重新赋值一个新的函数, 这个新函数中调用了原有函数, 并在原有函数的基础上添加了新的内容, 当通过该函数名调用时, 会执行更多的功能, 这就是装饰器的作用

    七、进阶

    1、装饰器叠加
    def printStar(func):
        print("1111")
        def inner():
            print("*" * 30)
            func()
        return inner
    
    def printLine(func):
        print("2222")
        def inner():
            print("-" * 30)
            func()
        return inner
    
    @printStar
    @printLine
    def name():
        print("name")
    
    # 打印结果
    2222
    1111
    
    • 可以得出, 当多个装饰器同时修饰同一个函数式, 距离函数最新的装饰器最先被加载
    name = printLine(name)
    name = printStar(name)
    
    • 此时我们直接调用name函数
    def printStar(func):
        def inner():
            print("*" * 30)
            func()
        return inner
    
    def printLine(func):
        def inner():
            print("-" * 30)
            func()
        return inner
    
    @printStar
    @printLine
    def name():
        print("name")
    
    name()      # 调用
    
    # 打印结果:
    ******************************
    ------------------------------
    name
    
    • 由上面的打印可以知道, 距离函数最远的装饰器, 在函数被调用时, 最先被执行
    2、对有参函数进行装饰
    • 装饰目标:
    def sum(num1, num2)
        print(num1, num2)
    
    • 装饰器
    def decorator(func):
        def inner(*args, **kwargs):
            func(*args, **kwargs)
        return inner
    
    • 结果:
    def decorator(func):
        def inner(*args, **kwargs):
            print("-" * 20)
            func(*args, **kwargs)
        return inner
    
    @decorator
    def sum(num1, num2):
        print(num1, num2)
        
    sum(1, 2)
    
    # 打印结果:
    --------------------
    3
    
    3、对有返回值的函数进行装饰, 无论什么场景, 保持函数返回值一致
    • 装饰目标:
    def sum(num1, num2):
        return num1 + num2
    
    • 装饰器
    def decorator(func):
        def inner(*args, **kwargs):
            result = func(*args, **kwargs)
            return result
        return inner
    
    • 结果
    def decorator(func):
        def inner(*args, **kwargs):
            print("-" * 20)
            result = func(*args, **kwargs)
            return result
        return inner
    
    @decorator
    def sum(num1, num2):
        return num1 + num2
    
    print(sum(num1 = 1, num2 = 2))
    
    # 打印结果:
    --------------------
    3
    
    4. 带有参数的装饰器
    • 装饰目标
    def name1():
        print("zhangsan")
        
    def name2():
        print("lisi")
    
    • 目标: 在打印zhangsan之前, 先打印20个*, 在打印lisi之前, 先打印20个-

    • 装饰器

    def symbol(char):
        def decorator(func):
            def inner():
                print(char * 20)
                func()
            return inner
        return decorator
    
    • 结果:
    def symbol(char):
        def decorator(func):
            def inner():
                print(char * 20)
                func()
            return inner
        return decorator
    
    @symbol("*")
    def name1():
        print("zhangsan")
    
    
    @symbol("-")
    def name2():
        print("lisi")
    
    name1()
    name2()
    
    # 打印结果: 
    ********************
    zhangsan
    --------------------
    lisi
    

    通过@装饰器(参数)的方式, 调用这个函数, 并传递参数, 并把返回值, 再次当做装饰器进行使用

    先计算@后面的内容, 并把这个内容当做是装饰器

    注意: 装饰器@xxxx, 后面的xxxx的结果必须是一个函数, 并且这个函数只能拥有一个参数, 用来接收函数名

    执行@symbol("*")时, 会先执行symbol("*"), 然后将执行结果作为装饰器, 即decorator

    相关文章

      网友评论

          本文标题:Python基础(十二): 装饰器

          本文链接:https://www.haomeiwen.com/subject/ohfxfftx.html