• (9)装饰器


    1. 什么是装饰器
    装饰器就是用来为被装饰的对象新增功能的工具/函数,被装饰的对象可以是任意可调用的对象,装饰器本身也可以是任意可调用的对象

    2. 为何要用装饰器
    开放封闭原则:对修改封闭,对扩展开放

    装饰器需要遵循的原则:
    1. 不能修改被装饰对象的源代码
    2. 不能修改被装饰对象的调用方式

    装饰器模板

    无参装饰器

    def outter(func):
      def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
      return wrapper

    有参装饰器

    def auth(这里写需要传入的参数):

      def outter(func):  #func就是传入最原始的函数的内存地址,其实就是将被装饰的函数功能传入
        def wrapper(*args,**kwargs):
          res=func(*args,**kwargs)
          return res
        return wrapper

      return outter

    @auth(这里写需要传入的参数) #装饰语法的后面括号里加参数,即可传入

    PS:如要添加功能可在装饰器的res=func(*args,**kwgars)的前后加入需要的功能

    PS:有参装饰器最多三层,有参的在三层装饰器最顶部可以传入无限个参数

    PS:装饰器的目标:就是要在遵循原则1和2的前提下,为被装饰对象新增功能

    3. 如何实现装饰器

    import time

    def index():  #这是被装饰对象
      time.sleep(1)
      print('welcome to index page')

    def timmer(func):  # func=最原始那个index的内存地址,func可以理解为就是被装饰的函数,当做参数传入装饰器  #这个就是装饰器格式
      def wrapper():
        start=time.time()
        func()
        stop=time.time()
        print('run time is %s' %(stop - start))
      return wrapper

    index=timmer(index)    #index=wrapper(index的内存地址),这两个index不一样,前面的是一个新的名称空间,后面的index做了一个转换  #这一步就是用来包装,让使用者感觉没有变化但是后台却对功能进行了一个升级
    index()   

    PS:为什么要在嵌套函数的外面再加一层函数,因为如果不加这层函数,则最后包装调用时候无法传入index整个参数

    装饰器语法糖

    格式:在被装饰对象正上方单独一行写@ + 装饰器的名字(例:@timmer)

    其实就是装饰器(正下方的那个函数当做参数传入装饰器将返回的值赋值给原函数名)

    用来取代这个操作index=timmer(index),其实就是一种简化语法

     例:无参装饰器

    import time   

    def timmer(func):   #这个是装饰器,用来将index函数当做参数传入

      def wrapper():

        start=time.time()

        func()

        stop=time.time()

        print('run time is %s' %(stop - start))

      return wrapper

    @timmer

    def index():

      time.sleep(1)

      print('welcome to index page')

    index()

    PS:语法糖的步骤解析

    1、写好装饰器先要将被装饰对象index函数当做参数传给装饰器timmer(即timmer(index))

    2、然后将装饰器(index)再赋值给原函数名index(即index = timmer(index))

    PS:所以@timmer = (index = timmer(index)),这一条是解析过程的解释,不是语法,请不要搞混

    PS:被装饰器一定要写在装饰器的下面,不然语法糖无法正确执行

    例:有参装饰器

    import time

    def timmer(func):
      def wrapper(*args,**kwargs):
        start=time.time()
        func(*args,**kwargs)
        stop=time.time()
        print('run time is %s' %(stop - start))
      return wrapper


    @timmer
    def home(name):
      time.sleep(2)
      print('welcome %s to home page' %name)

    index()

    PS:当被装饰对象是有参数的,那么必须在装饰器的局部函数加上*和**两个可变长参数,在内部调用的函数也加上可变长参数,这样在传入的时候不管是什么参数都可以正确传入

    PS:有参装饰器既能修饰有参的函数,也能修饰无参的函数

    例:有参且有返回值的装饰器

    import time

    def timmer(func):
      def wrapper(*args,**kwargs):
        start=time.time()
        res = func(*args,**kwargs)  #需要查看返回值必须将执行函数放入一个变量
        stop=time.time()
        print('run time is %s' %(stop - start))
        print('>>>>',res)
        return res  #在末尾返回这个变量
    return wrapper

    @timmer  #这个是没有返回值的被装饰函数
    def index():
      time.sleep(1)
      print('welcome to index page')

    @timmer  #这个是有返回值的被装饰器
    def home(name):
      time.sleep(2)
      print('welcome %s to home page' %name)
      return 11111  #返回值是1111


    index()
    home('karl')  #这里的实参是传递给最原始的函数,就是传递给home(name)这个形参的,让函数内部调用

    PS:需要装饰器返回被装饰函数的值,在装饰器内直接将调用的函数放入变量即可,然后在末尾返回这个变量

    多层有参装饰器

    import time
    current_user={'username':None}

    def outter(engine='file'):
      def auth(func):
        def wrapper2(*args,**kwargs):
          if current_user['username']:
            res = func(*args, **kwargs)
            return res
          name=input('username>>: ').strip()
          pwd=input('password>>: ').strip()
          if engine == 'file':
            print('基于文件的认证')
            if name == 'egon' and pwd == '123':
              current_user['username']=name
              res=func(*args,**kwargs)
              return res
            else:
              print('账号密码错误...')
          elif engine == 'mysql':
            print('基于mysql的认证')
          elif engine == 'ldap':
            print('基于ldap的认证') 
          else:
            print("不合法的认证源")
        return wrapper2
       return auth

    @outter(engine='file') #这个装饰器就是装饰下方的函数,括号里就是将参数传入装饰器,调用下面函数的时候就会将括号里的参数传入装饰器,触发功能
    def index():
      print('index function')
      time.sleep(1)

    @outter(engine='mysql')
    def home(name):
      print('home function',name)
      time.sleep(1)

    index()
    home('egon') #这里的egon是传给home函数内的功能调用的

    装饰器的伪装

    就是将装饰器的信息伪装成和被装饰函数的信息一抹一样

    在装饰器内的函数顶部加一个@wraps即可

    import time

    from functools import wraps

    def timmer(func):
      @wraps(func)  #就是在这个位置加@wraps
      def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print('run time is %s' %(stop - start))
        return res
      return wrapper

    def index():
    """
    这是一个index函数
    :return:
    """
    time.sleep(1)
      print('welcome to index page')

    index()

    装饰器叠加多个装饰器

    装饰器的加载顺序:自下而上

    装饰器的执行顺序:

    加载顺序实例:运行结果就是3,2,1

    import time

    def deco1(func1): #func1=wrapper2的内存地址
      print('deco1运行')
      def wrapper1(*args,**kwargs):
        res=func1(*args,**kwargs)
        return res
      return wrapper1

    def deco2(func2): #func2=wrapper3的内存地址
      print('deco2运行')
      def wrapper2(*args,**kwargs):
        res=func2(*args,**kwargs)
        return res
      return wrapper2

    def deco3(func3): #func3=最原始那个index的内存地址
      print('deco3运行')
      def wrapper3(*args,**kwargs):
        res=func3(*args,**kwargs)
        return res
      return wrapper3

        # index=wrapper1
    @deco1 # index=deco1(wrapper2)
    @deco2 # wrapper2=deco2(wrapper3)
    @deco3 # wrapper3=deco3(index) 最原始那个index的内存地址

    def index():
      print('index function')
      time.sleep(1)

    index()

    PS:装饰器上方有多个装饰器,加载的时候的deco3先把index最原始的那个内存地址传入,然后把结果赋值给wrapper3,然后把wrapper3传入deco2,把结果返回给wrapper2,然后把wrapper2传入deco1,把结果返回给index,最后运行index得到一个结果

    装饰器执行实例:运行结果就是1,2,3

    import time

    def deco1(func1): #func1=wrapper2的内存地址
      def wrapper1(*args,**kwargs):
        print('wrapper1')
        res=func1(*args,**kwargs)
        return res
      return wrapper1

    def deco2(func2): #func2=wrapper3的内存地址
      def wrapper2(*args,**kwargs):
        print('wrapper2')
        res=func2(*args,**kwargs)
        return res
      return wrapper2

    def deco3(func3): #func3=最原始那个index的内存地址
      def wrapper3(*args,**kwargs):
        print('wrapper3')
        res=func3(*args,**kwargs)
        return res
      return wrapper3


        # index=wrapper1
    @deco1 # index=deco1(wrapper2)
    @deco2 # wrapper2=deco2(wrapper3)
    @deco3 # wrapper3=deco3(最原始那个index的内存地址)

    def index():
      print('index function')
      time.sleep(1)

    index() #wrapper1()

    PS:运行的时候,deco1(wrapper1)调用了deco2(wrapper2),deco2(wrapper)调用了deco3(wrapper3),deco3(wrapper3)调用了index

    装饰器的装饰也是有顺序的,顺序的不同也会影响功能触发

    import time

    current_user={'username':None}

    def timmer(func):  #这是一个运行时间的装饰器

      def wrapper1(*args,**kwargs):

        start=time.time()

        res=func(*args,**kwargs)

        stop=time.time()

        print(stop - start)

        return res

      return wrapper1

    def auth(func):  #这是一个账户认证的装饰器

       def wrapper2(*args,**kwargs):

        if current_user['username']:

          res = func(*args, **kwargs)

          return res

        name=input('username>>: ').strip()

        pwd=input('password>>: ').strip()

        if name == 'egon' and pwd == '123':

          current_user['username']=name

          res=func(*args,**kwargs)

          return res

        else:

          print('账号密码错误...')

      return wrapper2

    @timmer #这个装饰器是装饰下面的auth装饰器的,而auth装饰器是装饰index的

    @authdef

    index():

      print('index function')

      time.sleep(1)index()

    PS:如果@timmer写在@auth的上面,name这个装饰器就是用来统计用户验证的时间的,如果写在@auth的下面,则就是用来装饰index这个函数,统计index函数内功能运行的时间,所以装饰器的位置不同,也会对触发的功能有所影响

    判断装饰器的执行顺序(小窍门):

    装饰器调用的时候肯定是先加载,再执行

    所以可以根据环形图知道加载的时候,先生成了这样的环形图,

    1、index先传给了wrapper3(最原始的那个index)

    2、将wrapper3传给了wrapper2

    3、将wrapper2传给了wrapper1

    PS:其实就是加载时候wrapper3(deco3)得到了最原始的index的内存地址后将wrapper2(deco2)包起来,wrapper2(deco2)将wrapper1(deco1)包起来,这时候wrapper1(deco1)就获取到了最原始的index的内存地址。

    加载结束后,我们将最里层的赋值给了最index这个函数名(这个index和最原始的index这个函数无任何关系),然后我们执行的时候,就是先调用了最里层的那个wrapper1(deco1),wrapper1(deco1)调用了wrapper2(deco2),wrapper2(deco2)调用了wrapper3(deco3),wrapper3(deco3)最开始得到的就是最原始的index的内存地址,最终结果我们得到了最原始的index函数的功能,在调用过程中也实现的新功能的传递

    PS:环形图的意思就是我们调用的时候wrapper1调用了wrapper2,wrapper2调用了wrapper3(最原始的那个index的内存地址),然后返回给wrapper2,wrapper2返回给wrapper1,wrapper1返回给了index,调用到了最原始的index内存地址,即没有改变原程序的代码,也没有改变调用方式,用户执行时候还是执行index就可以得到最原始的功能,也能得到新功能

  • 相关阅读:
    SAP CRM WebClient UI的Delta处理机制介绍
    三种动态控制SAP CRM WebClient UI assignment block显示与否的方法
    SAPGUI软件里做的设置,本地操作系统保存的具体位置
    SAP CRM附件在应用服务器上的存储原理解析
    FLINK实例(2):CONNECTORS(1)如何正确使用 Flink Connector?
    shell脚本执行报错:/bin/bash^M: bad interpreter: No such file or directory
    FLINK实例(6): CONNECOTRS(5)Flink Kafka Connector 与 Exactly Once 剖析
    java.lang.IllegalStateException(Connection to remote Spark driver was lost)
    java.security.cert.CertificateNotYetValidException: NotBefore
    Hadoop问题:org.apache.hadoop.ipc.RpcException: RPC response exceeds maximum data length 错误
  • 原文地址:https://www.cnblogs.com/shizhengquan/p/9977495.html
Copyright © 2020-2023  润新知