(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就可以得到最原始的功能,也能得到新功能

原文地址:https://www.cnblogs.com/shizhengquan/p/9977495.html