• Python之路(第十一篇)装饰器


    一、什么是装饰器?

      装饰器他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。
    强调装饰器的原则:1 不修改被装饰对象的源代码 2 不修改被装饰对象的调用方式
    装饰器的目标:在遵循1和2的前提下,为被装饰对象添加上新功能

    二、软件开发的原则

    软件开发 的 一个原则:“开放-封闭”原则 开放:对现有功能的扩展开放 封闭:已实现的功能代码块不应该被修改

    三、装饰器的应用

    装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

    概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能

    四、装饰器的知识储备

    装饰器 = 高阶函数+函数嵌套+闭包

    高阶函数定义:

    1、函数接收的参数是一个函数名

    2、函数的返回值是一个函数

    3、满足上述条件任意一个,都可以称之为高阶函数

    五、闭包

    在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。

    在python语言中形成闭包的三个条件,缺一不可:1)必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套2)内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量3)外部函数必须返回内嵌函数——必须返回那个内部函数

    一般情况下,如果一个函数运行结束了,内存中将不会存储任何关于该函数的东西,函数的局部变量都会消失,但是闭包是一个特殊的情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

    例子

      def outer():
          name = "nicholas"
          def inner(): #  inner()是内函数
              print(name)  #在内函数中 用到了外函数的临时变量
              print("里面的一层", locals())
          print("外面的一层", locals())
          return inner  #外函数的返回值是内函数的引用
      ​
      a = outer()
      a() #执行函数内部的inner()函数
    

      

    六、装饰器

    最简单的装饰器

      
      def test(func):  #参数中直接是函数名
          def wrapper():  #函数嵌套
              print("装饰器")
              print("----")
              func()
          return wrapper  #返回值是函数名
      ​
      ​
      def func1():
          print("from func1")
      ​
      func1 = test(func1)   #将test()函数的返回值wrapper函数的内存地址重新赋值给func1
      func1()   #这里执行的func1()其实是在wrapper()执行函数里,没有改变原func1()函数的调用方式,
                #但是同样执行了原函数
    

      


    使用语法糖、对原函数有参数的函数进行装饰,为装饰器函数加上返回值

      
      def test(func):  #参数中直接是函数名
          def wrapper(*args,**kwargs):  #函数参数加入可变参数*args和关键字参数**kwargs
              # 有了这两个参数,装饰器就可以用于任意目标函数了。
              print("装饰器")
              print("----")
              res =func(*args,**kwargs)  #将函数结果赋值给res,并返回res,获取被装饰函数的返回值
              return res
          return wrapper  #返回值是函数名
      ​
      @test  #这里相当执行了func1 = test(func1)语句,这就是语法糖,写在要装饰的函数上面
      def func1():  #这不传入参数
          print("from func1")
      ​
      @test  #这里相当于执行了 func2 = test(func2)语句,这就是语法糖
      def func2(name,age):  #这里传入2个位置参数
          print("from func2")
          print(name)
          print(age)
      ​
      @test
      def func3(name,age,gender):  #这里是传入了3个关键字参数
          print("from func3")
          print(name,age,gender)
      ​
      @test
      def func4(name,age,gender):  #传入2个位置参数,1个关键字参数,注意关键字参数应该在位置参数之后
          print("from func4")
          print(name, age, gender)
          
      func1()   #这里执行的func1()其实是在wrapper()执行函数里,没有改变原func1()函数的调用方式
      print("----func函数之间分割线---")
      func2("nick",18)
      print("----func函数之间分割线---")
      func3(name = "nicholas",age = 18,gender = "male")
      print("----func函数之间分割线---")
      func4("jack",53,gender="male")
    

      

    输出结果

      
      装饰器
      ----
      from func1
      ----func函数之间分割线---
      装饰器
      ----
      from func2
      nick
      18
      ----func函数之间分割线---
      装饰器
      ----
      from func3
      nicholas 18 male
      ----func函数之间分割线---
      装饰器
      ----
      from func4
      jack 53 male
    

      

    七、解压序列赋值

    需求:现在有一个包含N个元素的元组或者是序列,怎样将它里面的值解压后同时赋值给N个变量

      
      li  = [2,7,3,8]
      a,b,c,d = li  #多个变量同时赋值,注意左边的变量个数要与右边的元素个数相等,如果不相等会报错
      print(a,b,c,d)
    

      

    输出结果

      
      2 7 3 8
    

      

    例子2

      
      li  = [1,2,3,4,5,6,7,8,9,10]
      a,*b,c = li  #中间的*代表中间所有的,把中间所有的赋值给变量b,开头和结尾的元素分别赋值给a,c
      print(a)
      print(b)
      print(c)
    

      

    输出结果

      
      1
      [2, 3, 4, 5, 6, 7, 8, 9]
      10
    

      

    例子3

      
      li  = [1,2,3,4,5,6,7,8,9,10]
      a,b,*s,c,d = li  #取前2个和后2个,中间的直接赋值给s
      print(a)
      print(b)
      print(s)
      print(c)
      print(d)
    

      

    输出结果

      
      1
      2
      [3, 4, 5, 6, 7, 8]
      9
      10
    

      

    不关心的元素,可以用特殊变量(_)获取,不必使用

      
      li  = [1,2]
      a,_ = li #_下划线代表不关心的元素,可以丢掉的元素,注意左右两边元素数量相等
      print(a)
      ​
      li2 = [1,2,3,4,5]
      _,_,b,_,_ = li2
      print(b)
    

      

    八、模拟购物商城登录权限验证的装饰器

    简单版,功能函数只是做了简单处理,未写全

    实现功能,在每个功能函数执行前验证是否登录成功。

      
      userinfo = [{"name":"nicholas","passwd":"123"},
                  {"name":"jack","passwd":"456"},
                  {"name":"charles","passwd":"789"},
                  {"name":"richard","passwd":"147"},
                  ]
      #这里简单直接用列表加字典存储用户信息
      user_status = {"username":None,"status":False}
      #确定用户的登录状态
      ​
      ​
      def auth_func(func):
          def wrapper(*args,**kwargs):
              if user_status["username"] and user_status["status"]: #判断用户之前是否登录成功,
                  # 如果是登录成功继续执行其他功能函数
                  res = func(*args,**kwargs)
                  return res  #返回功能函数的返回值
              user_name = input("请输入您的账户名:").strip()  #没有登录成功就要求用户输入账户名密码
              user_passwd = input("请输入您的密码:").strip()
      ​
              for i in userinfo:  #for循环遍历确定用户名密码是否正确
                  if user_name == i["name"] and user_passwd == i["passwd"]:
                      print("恭喜您,登录成功!%s"%user_name)
                      user_status["username"] = user_name  #改变用户登录的状态以便执行下一功能函数时可以直接执行,
                      # 不用再次登录
                      user_status["status"] = True
                      res = func(*args, **kwargs)
                      return res
              else:
                  """这里用for-else语句的一个特性,如果for循环语句中执行了return语句那么就不执行else语句,
                        如果没有执行到return语句那么就继续执行else语句,这里如果用for循环遍历没有符合if语句的账户名密码,
                        那就执行不了return语句,即在输入过程中用户输入的账户、密码不正确,这里就继续执行else语句显示
                        "账户密码不正确"正合适
                  """
                  print("您输入的账户或者密码不正确!")
          return  wrapper
      ​
      @auth_func  等价于index = auth_func(index)
      def index():
          #商城主页
          print("欢迎来到购物商城!")
      ​
      @auth_func
      def shopping_cart(name):
          #购物车模块
          print("%s的购物车有%s,%s"%(name,"物品1","物品2"))
      ​
      @auth_func
      def ord(someth1,someth2):
          #订单模块
          print("买的物品是%s %s"%(someth1,someth2))
      ​
      index()
      shopping_cart("nicholas")
      ord("CRV","基金")
    

      语法糖@auth_func,相当于将@下面的函数作为参数传入auth_func()函数中,并且使原函数名=auth_func(函数名)

    进一步升级,为装饰器函数增加参数(不是针对原函数的参数进行处理)

      userinfo = [{"name":"nicholas","passwd":"123"},
                  {"name":"jack","passwd":"456"},
                  {"name":"charles","passwd":"789"},
                  {"name":"richard","passwd":"147"},
                  ]
      #这里简单直接用列表加字典存储用户信息
      user_status = {"username":None,"status":False}
      #确定用户的登录状态
      ​
      def auth(sign):
              def auth_func(func):
                  def wrapper(*args,**kwargs):
                      if sign == "s1":
                          # print("模式1")
                          if user_status["username"] and user_status["status"]: #判断用户之前是否登录成功,
                              # 如果是登录成功继续执行其他功能函数
                              res = func(*args,**kwargs)
                              return res  #返回功能函数的返回值
                          user_name = input("请输入您的账户名:").strip()  #没有登录成功就要求用户输入账户名密码
                          user_passwd = input("请输入您的密码:").strip()
      ​
                          for i in userinfo:  #for循环遍历确定用户名密码是否正确
                              if user_name == i["name"] and user_passwd == i["passwd"]:
                                  print("模式1")
                                  print("恭喜您,登录成功!%s"%user_name)
                                  user_status["username"] = user_name  #改变用户登录的状态以便执行下一功能函数时可以直接执行,
                                  # 不用再次登录
                                  user_status["status"] = True
                                  res = func(*args, **kwargs)
                                  return res
                          else:
                              """这里用for-else语句的一个特性,如果for循环语句中执行了return语句那么就不执行else语句,
                                    如果没有执行到return语句那么就继续执行else语句,这里如果用for循环遍历没有符合if语句的账户名密码,
                                    那就执行不了return语句,即在输入过程中用户输入的账户、密码不正确,这里就继续执行else语句显示
                                    "账户密码不正确"正合适
                              """
                              print("您输入的账户或者密码不正确!")
                      elif sign == "s2":
                          print("模式2")
                          #这里写一些逻辑功能模块加再次对之前的功能函数进行封装
                      else:
                          print("模式3")
                          # 这里写一些逻辑功能模块加再次对之前的功能函数进行封装
      ​
                  return wrapper  #返回内层装饰器封装的函数
              return auth_func  #返回外层装饰器封装的函数
      ​
      @auth(sign="s1")  #这里的"s1"是个无意义的参数,可根据具体需求情况修改
                        # 这里等价于 index = auth("s1")(index),即先执行index = auth("s1")返回auth_func函数的
                        # 内存地址temp,然后再执行index =temp(index),这里即可返回wrapper函数的内存地址
      ​
      def index():
          #商城主页
          print("欢迎来到购物商城!")
      ​
      @auth(sign="s1")  #等价于 shopping_cart = auth("s1")(shopping_cart)
      def shopping_cart(name):
          #购物车模块
          print("%s的购物车有%s,%s"%(name,"物品1","物品2"))
      ​
      @auth(sign="s1")  # ord = auth("s1")(ord)
      def ord(someth1,someth2):
          #订单模块
          print("买的物品是%s %s"%(someth1,someth2))
      ​
      index()
      shopping_cart("nicholas")
      ord("CRV","基金")
    

      

    auth函数实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。

    九、装饰器的执行过程

    单个装饰器函数的执行过程

    例子

      
      def auth(func):
          print("1")
          def wrapper():
              func()
              print("a")
          print("2")
          return wrapper
      ​
      ​
      @auth #  func1 = auth(func1)
      def func1():
          print("b")
      ​
      @auth #  func2 = auth(func2)
      def func2():
          print("c")
      ​
      func1()
      func2()
    

      

    输出结果

      
      1
      2
      1
      2
      b
      a
      c
      a
      ​
    

      

    分析执行过程:

    1、将auth函数加载到内存不执行

    2、执行第一个@auth语法糖,即执行func1 = auth(func1),将func1作为参数运行函数auth()函数,打印输出1,

    加载wrapper()函数到内存,打印输出2,由于存在闭包,将临时变量func = func1存入 wrapper()函数的执行环境,返回wrapper函数的内存地址,auth函数执行完毕,关闭函数。

    3、执行第二个@auth语法糖,即执行func2 = auth(func2),将func2作为参数运行函数auth()函数,打印输出1,

    加载wrapper()函数到内存,打印输出2,由于存在闭包,将临时变量func = func2存入 wrapper()函数的执行环境,返回wrapper函数的内存地址,auth函数执行完毕,关闭函数。

    4、执行func1()函数,即这里执行的是第一个语法糖@auth返回的wrapper函数,这里包含了一个临时变量

    func = func1,即执行func1(),打印输出b,之后执行print("a")语句打印输出a,

    5、执行func2()函数,即这里执行的是第二个语法糖@auth返回的wrapper函数,这里包含了一个临时变量

    func = func2,即执行func2(),打印输出c,之后执行print("a")语句打印输出a。

    多个装饰器函数的执行过程

    装饰顺序按靠近函数顺序执行,调用时由外而内,执行顺序和装饰顺序相反,而非按照一般程序的执行顺序。

    例子

      def auth(x):
          print("1")
          def wrapper():
              x()
              print("a")
          print("2")
          return wrapper
      ​
      ​
      def auth2(y):
          print("3")
          def wrapper2():
              y()
              print("b")
          print("4")
          return wrapper2
      ​
      ​
      @auth 
      @auth2
      def func():
          print("c")
      ​
      func()
    

      

    输出结果

      
      3
      4
      1
      2
      c
      b
      a
     
    

      

    分析:

    执行过程

    1、加载函数auth()到内存,不执行

    2、加载函数auth2()到内存,不执行

    3、执行语法糖@auth @auth2,先执行@auth2,这里执行顺序是按靠近函数顺序执行,即先执行靠近函数的@auth2,而非一般的程序执行顺序 。这里等价于func = auth(auth2(func))。执行@auth2,执行auth2()函数,打印输出3,加载wrapper2()函数到内存,打印输出4,由于存在闭包,将临时变量y=func存入wrapper2()函数,返回wrapper2()函数的内存地址,auth2()函数结束。

    此时func = wrapper2()函数的内存地址,即func = auth2(func)。

    4、执行语法糖@auth,执行auth()函数,打印输出1,加载wrapper()函数到内存,打印输出2,加载wrapper()函数到内存,打印输出2,由于存在闭包,将临时变量x = auth2(func)即(语法糖@auth2返回的内存地址),存入wrapper()函数,返回wrapper()函数的内存地址,auth()函数结束。

    此时func = wrapper()函数的内存地址,即func = auth(auth2(func))。

    5、执行func()语句,即执行auth(auth2(func))(),首先执行的auth()函数,auth(auth2(func))整体是第一个装饰器返回的wrapper()函数的内存地址,即这里要执行auth()中的wrapper()函数,此时auth()参数x是auth2(func),执行到x()语句,这里其实是要执行auth2(func)(),而auth2(func)是返回的wrapper2()函数的内存地址,于是从第一个装饰器函数中跳转到第二个装饰器函数,即这里要执行wrapper2()函数,这里的y参数是func,即执行真正的func()函数,打印输出c,然后继续执行wrapper2()函数内的语句print("b")打印输出b,打印输出b,wrapper2()函数执行完成,即第一个装饰器函数中的wrapper()函数中的x()语句执行完成。

    6、继续执行第一个装饰器函数中的wrapper()函数,执行print("a")语句,打印输出a,程序结束。

     

  • 相关阅读:
    仿美团pc,koa+ssr(四)
    基本的数据库操作脚本
    jQuery选择器总结
    经常学习参考网站
    WebAPI
    Sqlserver 基本面试题
    truncate和delete的区别
    Webservice,WCF,WebAPI 之间的区别
    WCF 学习
    学习angularJs(1)--引用文件
  • 原文地址:https://www.cnblogs.com/Nicholas0707/p/8953291.html
Copyright © 2020-2023  润新知