翼度科技»论坛 编程开发 python 查看内容

Python装饰器实例讲解(二)

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
Python装饰器实例讲解(二)

Python装饰器实例讲解(一)
你最好去看下第一篇,虽然也不是紧密的链接在一起
参考B站码农高天的视频,大家喜欢看视频可以跳转忽略本文:
一键三连哦
本文的知识点主要是
​            类装饰器
​        装饰器的本质(up主说的万能公式)
案例


  • 代码
    1. def count_time(func):
    2.     def wrapper(*args,**kwargs):
    3.         from time import time
    4.         start_time = time()
    5.         result = func(*args,**kwargs)
    6.         end_time = time()
    7.         print(f'统计花了{end_time-start_time}时间')
    8.         return result
    9.     return wrapper
    复制代码
  • 改造为类装饰器(注意对比)

    • 你得知道基础的python的面向对象的知识
    • 一些类的魔术方法如__init__和__call__
    1. class CountTime:
    2.     def __init__(self,function_name):  # 类没传参一说,但实例化是可以传参的,类比  def count_time(func):
    3.         self.function_name = function_name
    4.     def __call__(self, *args, **kwargs):  # 类实例的(),像函数的call , ==>def wrapper(*args,**kwargs):
    5.         from time import time
    6.         start_time = time()
    7.         result = self.function_name(*args,**kwargs)  # 也就改了这里,其他都一样
    8.         end_time = time()
    9.         print(f'统计花了{end_time-start_time}时间')
    10.         return result
    复制代码
  • 完整的代码
    1. def is_prime(x):
    2.     if  x == 2 :
    3.         return True
    4.     elif x % 2 == 0 or x == 1 :
    5.         return False
    6.     for i in range(3, int(x ** 0.5) + 1, 2):
    7.         if x % i == 0:
    8.             return False
    9.     return True
    10. class CountTime:
    11.     ... # 就不重复上面了
    12. @CountTime  # 类是一个装饰器
    13. def get_prime_nums(start,end):
    14.     prime_nums = 0
    15.     for num in range(start,end):
    16.         if is_prime(num):
    17.             prime_nums = prime_nums + 1
    18.     return prime_nums
    19. print(get_prime_nums(2,50000))  # 效果是一样的
    复制代码
码农高天说

我把up主的一些话摘录一些写到这里,辅助大家理解


  • 装饰器decorator:是一个输入是函数,输出也是函数的函数(看讲解一中的装饰器)
  • 类装饰器 class decorator,up主说有一定的歧义

    • 可以当做装饰器的类(装饰器本身)
    • 可以装饰类的装饰器(装饰器要装饰的对象)

  • 装饰器本身既可以是函数也可以是类,装饰的对象同样可以是函数或者类
  • 背这些理论没有意义,关键要弄懂背后的原理
  • __call__可以让类的实例当做函数用(就是callable)
万能公式


  • 装饰器语法糖背后
    1. class CountTime:
    2.     ...# 同上
    3. @CountTime
    4. def add(a,b):  # 就用码农的demo函数
    5.     return a+b
    6. print(add(1,2))
    复制代码
  • @CountTime等价于,所谓的万能公式
    1. add = CountTime(add)
    复制代码
  • print(add(1,2))已经不再是使用的原始的add了,用的是新的add
    1. print(add(1,2)) 等价于
    2. print(CountTime(add)(1,2))
    复制代码
  • 也就是说
    1. # @CountTime  # 去掉装饰器,你就是定义了一个简单的函数
    2. # # add = CountTime(add) # 函数名被重定义了  相当于这样
    3. def add(a,b):
    4.     return a+b
    5. print(CountTime(add)(1,2))
    复制代码
  • 你还可以这样
    1. def add(a,b):
    2.     return a+b
    3. new_add = CountTime(add)
    4. print(new_add(1,2))
    复制代码
  • 是的,被装饰过的函数已经不再是原来的函数了,它总是会先去执行装饰器(CountTime(add))
  • 总结:

    • 在一个函数上做装饰器,等价于装饰器调用这个函数
    • 在类装饰器的这个例子中,add从一个函数变成了一个类的实例(type看下即可)

改造,有参数的装饰器


  • 我们看到过很多的装饰是有参数的,这是怎么实现的呢?
  • 比如你想要输出的信息可以调整其前缀
    1. 计时: 0.46秒
    2.         或者
    3. 用时: 0.46秒
    复制代码
  • 你希望是这样装饰和调用的
    1. @CountTime(prefix='用时:')
    2. def add(a,b):
    3.     return a+b
    4. print(add(1,2))
    复制代码
  • 那咋实现呢?
  • 回到万能公式:
    1. @CountTime(prefix='用时:')
    2. def add(a,b):
    3.     ...
    4. # 等价于
    5. add = CountTime(add)
    6. # 那么
    7. @CountTime(prefix='用时:')
    8. def add(a,b):
    9.     ...
    10.    
    11. # 等价于
    12. add = CountTime(prefix='用时:')(add)
    复制代码
  • CountTime这个类能CountTime(prefix='用时:'),就是实例化做到的,所以...类的init方法要改一下,不再是传参function_name了,而是传你的prefix,像这样
    1. class CountTimeV2:
    2.     def __init__(self,prefix='用时:'):
    3.         self.prefix = prefix
    复制代码
  • 但现在还不能继续,add = CountTime(prefix='用时:')(add)中你(add)还要处理,前面是init做的,()就是callable做的,里面的参数是add,也就是函数的名字,所以你的call也要改造,像这样吗?
    1.     def __call__(self, function_name):
    2.         from time import time
    3.         start_time = time()
    4.         result = function_name(*args,**kwargs)
    5.         end_time = time()
    6.         print(f'统计花了{end_time-start_time}时间')
    7.         return result
    复制代码
  • 不对的,光这样改造不够的,因为你这个function_name(*args,**kwargs)在IDE中就会报错,哪里来的呢?没有定义。
  • 回想讲解一中,函数装饰器里层,还有一个函数,此处就可以参考
    1. class CountTimeV2:
    2.     def __init__(self, prefix='用时:'):
    3.         self.prefix = prefix
    4.     def __call__(self, function_name):
    5.         def wrapper(*args, **kwargs):  # 加了个函数 , 包裹一层
    6.             from time import time
    7.             start_time = time()
    8.             result = function_name(*args, **kwargs) # 这样就可以用参数了
    9.             end_time = time()
    10.             print(f'{self.prefix}{end_time - start_time}')  # 用之前的定义
    11.             return result
    12.         return wrapper
    13. @CountTimeV2(prefix='耗时:')  # 可以改为用时、计时等等
    14. def add(a, b):
    15.     return a + b
    16. print(add(1, 2))
    复制代码
前面谈的是类是一个装饰器,装了一个函数
下面谈的是函数是一个装饰器,装饰一个类
类的装饰器


  • 现在有这么一个类
    1. class Person:
    2.     pass
    3. wuxianfeng = Person()
    4. print(wuxianfeng)  # <__main__.Person object at 0x000002361C15A460>
    复制代码
  • 你学过python可以这样修改
    1. class Person:
    2.     def __str__(self):
    3.         return f"{self.__class__.__name__}"
    4. wuxianfeng = Person()
    5. print(wuxianfeng) # Person
    复制代码
  • 但如果有很多的类都要如此呢?
  • 可以写个装饰器,来装饰这些类呗
  • 怎么写?回想刚才你学到的知识,万能公式!
    1. def show_classname(): # 先不写参数
    2.     pass  # 先不写内容
    3. @show_classname
    4. class Person:
    5.     pass
    6. Person = show_classname(Person)
    7. wuxianfeng = Person()
    8. print(wuxianfeng)
    复制代码

    • 你现在要写一个函数,名字随意,如show_classname
    • 你肯定要装饰在类上
      1. @show_classname
      2. class Person:
      3.     pass
      复制代码
    • 根据万能公式,你的Person应该变了
      1. Person = show_classname(Person)
      2. # 从上面这段代码,你要能分析出以下内容
      3.     # 1. show_classname应该有个参数,传参是个类名
      4.     # 2. 因为可以Person = ,所以show_classname有个返回值
      复制代码
    • 对于使用者而言,应该没有任何操作上的差异
      1. wuxianfeng = Person()
      2. # 从上面这段代码,你要能分析出以下内容
      3. # 1. Person已经被你改变了
      4. # 2. Person()==>show_classname(Person)(),所以show_classname这个函数的返回值还是一个类
      5. print(wuxianfeng)
      复制代码
    • 分析完了,函数体部分是有点不好理解的
      1. def show_classname(class_name):    def __str__(self):        return self.__class__.__name__    class_name.__str__ = __str__    return class_name@show_classname
      2. class Person:
      3.     passPerson = show_classname(Person)wuxianfeng = Person()print(wuxianfeng)
      复制代码
    • 看着这个结果,我们来解释下(也许你会更好理解)
      1. 1. show_classname(Person) 返回仍然是Person
      2. 2. 但这个时候的Person被改变了一点(你要做的不就是如此吗?)
      3. 3. 原来你是这样写的
      4.     class Person:
      5.         def __str__(self):
      6.             return f"{self.__class__.__name__}"
      7.     看看现在的写法
      8.     def __str__(self):
      9.         return self.__class__.__name__
      10.     class_name.__str__ = __str__
      11.          # 前面的class_name.__str__ 是类自己的函数(本段解释的line 5)
      12.          # 后面的= __str__ ,是line8的函数
      13.          # 是的,函数可以被重新赋值,函数是一等对象,
      复制代码
    • 如果还不明白...尽力了

带参数的类的装饰器

码农高天并没有给出示例代码
当然如果你真懂了前面的"改造,有参数的装饰器",也很简单


  • 直接上代码
    1. def show_classname(info='类名:'):
    2.     def wrapper(class_name):
    3.         def __str__(self):
    4.             return info+ self.__class__.__name__
    5.         class_name.__str__ = __str__
    6.         return class_name
    7.     return wrapper
    8. @show_classname('类的名字是:')  #
    9. class Person:
    10.     pass
    11. wuxianfeng = Person()
    12. print(wuxianfeng)
    复制代码
  • 默认值就是='类名:',怎么用呢
    1. @show_classname()
    2. class Human:
    3.     pass
    4. qianyuli = Human()
    5. print(qianyuli)
    复制代码
  • 注意不能这样
    1. @show_classname
    2. class Human:
    3.     pass
    4. qianyuli = Human()
    5. print(qianyuli)
    复制代码
  • 提示错误
    1. Traceback (most recent call last):
    2.   File "demo.py", line 21, in <module>
    3.     qianyuli = Human()
    4. TypeError: wrapper() missing 1 required positional argument: 'class_name'
    复制代码
  • 提个问题,为何会报错?
  • 如果你无法解释的通,你应该还没理解。
  • 答案其实还是万能公式。
    1. @show_classname
    2. class Human:
    3.     pass
    4. # 1.
    5. 等价于(万能公式来了)
    6. Human = show_classname(Human)
    7. # 2.
    8. show_classname(Human) 执行这个的时候其实你在做
    9. def show_classname(info='类名:'):
    10.     ...
    11. Human这个东西传给了info
    12. # 你要不信,你改为下面这样就知道了;信的话就过
    13.             def show_classname(info='类名:'):
    14.                 print('info是啥?',info.name)
    15.             class Human:
    16.                 name = '女娲'
    17.                
    18. # 3.
    19. show_classname(Human)这个的返回是wrapper
    20. 但wrapper这个函数是有个参数的,看你的定义def  wrapper(class_name):
    21.    
    22. # 4.
    23. 定义的时候是感知不到问题的,下面的报错行
    24. qianyuli = Human()
    25. 其实你是在
    26. Human()=>show_classname(Human)()=>wrapper(),错了,(看3),你需要一个class_name参数
    复制代码
  • 如果还不明白...尽力了

来源:https://www.cnblogs.com/wuxianfeng023/p/17106513.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具