装饰器 decorator

装饰器定义

在不改变原有函数代码,且保持原函数调用方法不变的情况下,给原函数增加新的功能(或者给类增加属性和方法)
核心思想:用一个函数(或者类)去装饰一个旧函数(或者类),造出一个新函数(或者新类)
应用场景:引入日志,函数执行时间的统计,执行函数前的准备工作,执行函数后的处理工作,权限校验,缓存等
语法规则:在原有的函数上加上 @符,装饰器会把下面的函数当作参数传递到装饰器中,@符又被成为 语法糖

1.装饰器原型(闭包)

  1. # 1。 装饰器的原型
  2. ### 利用闭包,把函数当作参数传递,并且在函数内去调用传递进来的函数,并返回一个函数
  3. # 定义外函数,接收一个函数作为参数
  4. def outer(f):
  5. # 定义内函数,并且在内函数中调用了外函数的参数
  6. def inner():
  7. print('我是外函数中的内函数1')
  8. f()
  9. print('我是外函数中的内函数2')
  10. return inner
  11. # 定义普通函数
  12. # def old():
  13. # print('我是一个普通的函数')
  14. #
  15. # # old() # 作为普通函数直接调用
  16. # old = outer(old) # outer返回了inner函数,赋值给了old
  17. # old() # 此时再调用old函数时,等同于调用了 inner 函数
  18. # 改为装饰器用法
  19. @outer # 此处使用的@outer的语法就是把outer作为了装饰器,等同于 old = outer(old)
  20. def old():
  21. print('我是一个普通的函数')
  22. old() # old函数经过 outer装饰器进行了装饰,代码和调用方法不变,但是函数的功能发送了改变

2.装饰器的应用:统计函数的执行时间

  1. # 装饰器应用场景-统计函数执行时间
  2. import time
  3. # 定义一个统计函数执行时间的 装饰器
  4. def runtime(f):
  5. def inner():
  6. start = time.perf_counter()
  7. f()
  8. end = time.perf_counter() - start
  9. print(f'函数的调用执行时间为:{end}')
  10. return inner
  11. # 定义一个函数
  12. @runtime
  13. def func():
  14. for i in range(5):
  15. print(i,end=" ")
  16. time.sleep(1)
  17. func()

3.装饰器嵌套语法

  1. # 1.定义装饰器
  2. # 外函数
  3. def outer(func):
  4. #内函数
  5. def inner():
  6. print('找到妹子,成功拿到微信。。。3')
  7. func() # 在内函数中调用外函数中的行参-函数
  8. print('约妹子,看一场午夜电影。。。4')
  9. # 在外函数中返回内函数
  10. return inner
  11. # 2。在定义一个装饰器
  12. def kuozhan(f):
  13. def kzinner():
  14. print('扩展1')
  15. f()
  16. print('扩展2')
  17. return kzinner
  18. # 3. 装饰器的嵌套 先执行下面的,再执行上面的。
  19. @kuozhan # 2。再使用上面的 kuozhan 装饰器,装饰 上一次返回的 inner 函数,又返回了 kzinner 函数
  20. @outer # 1。先使用离得近的 outer装饰器 装饰love函数,返回了一个 inner函数
  21. def love():
  22. print('跟妹子畅谈人生和理想。。。5')
  23. love()
  24. ''' 结果和过程的解析
  25. 1 3 5 4 2
  26. 1 先使用离得近的 outer装饰器 装饰love函数,返回了一个 inner函数
  27. 2 再使用上面的 kuozhan 装饰器,装饰 上一次返回的 inner 函数,又返回了 kzinner 函数
  28. 最后在调用love函数的时候是怎么执行的
  29. love() == kzinner()
  30. ===> 1
  31. ===> inner()
  32. ===> 3
  33. ===> love() ===> 5
  34. ===> 4
  35. ===> 2
  36. '''

4.对带有参数的函数进行装饰

  1. # 定义装饰器
  2. def outer(func):
  3. # 如果装饰器带有参数的函数,需要在内函数中定义行参,并传递给调用的函数。因为调用原函数等于调用内函数
  4. def inner(var):
  5. print(f'找到{var}妹子,成功拿到微信。。')
  6. func(var)
  7. print(f'约{var}妹子,看一场午夜电影。。')
  8. return inner
  9. # 有参数的函数
  10. @outer
  11. def love(name):
  12. print(f'跟{name}妹子畅谈人生。。。')
  13. love('思思') #love() ==> inner() love('思思') ===> inner('思思')

5.对多参数的函数进行装饰

  1. # 装饰带有多参数的函数
  2. def outer(func):
  3. def inner(who,name,*args,**kwargs):
  4. print('约到妹子,聊微信。。。')
  5. func(who,name,*args,**kwargs)
  6. print('天色一晚,怎么办?')
  7. return inner
  8. # 定义多参数的 函数
  9. @outer
  10. def love(who,name,*args,**kwargs):
  11. print(f'{who}跟{name}畅谈人生。。。')
  12. print('完事去吃了好多美食',args)
  13. print('看了一场电影',kwargs)
  14. love('三多','思思','火锅','辣条','7块钱的麻辣烫',mov='唐山大地震')
  15. '''
  16. love() ==> inner()
  17. love(...) ==> inner(...)
  18. inner(...) ==> love(...)
  19. '''

6.带有参数的装饰器

你会遇到带有参数的装饰器,例如Django框架中的 @login_required(login_url=’/accounts/login/‘)

  1. # 如果你的装饰器需要有参数,那么给当前的装饰器套一个壳,用于接收装饰器的参数
  2. def kuozhan(var):
  3. def outer(func):
  4. def inner1():
  5. print('妹子给了你微信')
  6. func()
  7. def inner2():
  8. print('妹子给介绍了个大妈')
  9. func()
  10. # 装饰器壳的参数,可以用于在函数内去做流程控制
  11. if var == 1:
  12. return inner1
  13. else:
  14. return inner2
  15. return outer
  16. @kuozhan(2) # kuozhan(var) ==> outer() ==> outer(love) ==> inner()
  17. def love():
  18. print('谈谈人生。。。')
  19. love()

7.用类装饰器装饰函数

  1. # 类装饰器装饰函数
  2. class Outer():
  3. # 魔术方法:当把该类的对象当作函数调用时,自动触发 obj()
  4. def __call__(self,func):
  5. self.func = func # 把传进来的函数作为对象的成员方法
  6. return self.inner # 返回一个函数
  7. # 在定义的需要返回的新方法中 去进行装饰和处理
  8. def inner(self,who):
  9. print('拿到妹子的微信。。。')
  10. self.func(who)
  11. print('看一场午夜电影。。。')
  12. @Outer() # Outer() ==> obj @obj==>obj(love) ==> __call__(love) ==> inner()
  13. def love(who):
  14. print(f'{who}和妹子谈谈人生和理想。。。')
  15. love('川哥') # inner('川哥')
  16. print(love) # 此时的 love就是属于Outer类这个对象中的inner方法

8.用类方法装饰函数

  1. # 用类方法装饰函数
  2. class Outer():
  3. def newinner(func):
  4. Outer.func = func # 把传递进来的函数定义为类方法
  5. return Outer.inner # 同时返回一个新的类方法
  6. def inner():
  7. print('拿到妹子微信')
  8. Outer.func()
  9. print('看一场午夜电影')
  10. @Outer.newinner # Outer.newinner(love) ==> Outer.inner
  11. def love():
  12. print('和妹子谈谈人生喝喝茶。。。')
  13. love() # love() ==> Outer.inner()

到目前为止以上所以形式的装饰器,包括 函数装饰器,类装饰器,类方法装饰器,都有一个共同特点:都是在给函数去进行装饰,增加功能。


用装饰器装饰类

还有一种装饰器,是专门装饰类的。也就是在类的定义的前面使用@装饰器这种语法
@装饰器
class Demo():
pass

装饰器给函数进行装饰,目的是不改变函数调用和代码的情况下给原函数增加了新的功能。

装饰器给类进行装饰,目的是不改变类的定义和调用的情况下给类增加新的成员(属性或方法)。

9.用函数装饰器装饰类

  1. # 使用函数装饰器,给类进行装饰,增加新的属性和方法
  2. # 定义函数,接收一个类。返回修改后的类
  3. def kuozhan(cls):
  4. def func2():
  5. print('我是在装饰器中追加的新方法,func2')
  6. cls.func2 = func2 # 把刚才定义的方法赋值给 类
  7. cls.name = '我是在装饰器中追加的新属性 name'
  8. #返回时,把追加类新成员的 类 返回去
  9. return cls
  10. @kuozhan # kuozhan(Demo) ==> cls ==> Demo
  11. class Demo():
  12. def func():
  13. print('我是Demo类中定义的func方法')
  14. Demo.func() # 此时在调用的Demo类是通过装饰器,更新过的Demo类
  15. Demo.func2()
  16. print(Demo.name)

10.使用类装饰器装饰类

  1. class KuoZhan():
  2. def __call__(self, cls):
  3. # 把接收的类,赋值给当前对象,作为一个属性
  4. self.cls = cls
  5. # 返回一个函数
  6. return self.newfunc
  7. def newfunc(self):
  8. self.cls.name = '我是在类装饰器中追加的新属性 name'
  9. self.cls.func2 = self.func2
  10. # 返回传递进来的类的实例化结果,obj
  11. return self.cls()
  12. def func2(self):
  13. print('我是在类装饰器中追加的新方法 func2')
  14. @KuoZhan() # KuoZhan() ==> obj ==> @obj(Demo) ==> __call__(Demo) ==> newfunc
  15. class Demo():
  16. def func(self):
  17. print('我是Demo类中定义的func方法')
  18. obj = Demo() # Demo() ==> newfunc() ==> obj
  19. obj.func()
  20. obj.func2()
  21. print(obj.name)
  22. # 思考: 此时的 obj这个对象,是哪个类的对象。Demo还是KuoZhan
  23. print(obj) # 此时的obj依然是Demo类的实例化对象,只不过经过装饰后,增加了新的属性和方法

作业:如何用一个装饰器,装饰带有返回值的函数