Python装饰器实例讲解(二)

1|0Python装饰器实例讲解(二)

Python装饰器实例讲解(一)

你最好去看下第一篇,虽然也不是紧密的链接在一起

参考B站码农高天的视频,大家喜欢看视频可以跳转忽略本文:https://www.bilibili.com/video/BV19U4y1d79C
一键三连哦
本文的知识点主要是

​ 类装饰器

​ 装饰器的本质(up主说的万能公式)

1|1案例

  • 代码

    def count_time(func): def wrapper(*args,**kwargs): from time import time start_time = time() result = func(*args,**kwargs) end_time = time() print(f'统计花了{end_time-start_time}时间') return result return wrapper
  • 改造为类装饰器(注意对比)

    • 你得知道基础的python的面向对象的知识
    • 一些类的魔术方法如__init__和__call__
    class CountTime: def __init__(self,function_name): # 类没传参一说,但实例化是可以传参的,类比 def count_time(func): self.function_name = function_name def __call__(self, *args, **kwargs): # 类实例的(),像函数的call , ==>def wrapper(*args,**kwargs): from time import time start_time = time() result = self.function_name(*args,**kwargs) # 也就改了这里,其他都一样 end_time = time() print(f'统计花了{end_time-start_time}时间') return result
  • 完整的代码

    def is_prime(x): if x == 2 : return True elif x % 2 == 0 or x == 1 : return False for i in range(3, int(x ** 0.5) + 1, 2): if x % i == 0: return False return True class CountTime: ... # 就不重复上面了 @CountTime # 类是一个装饰器 def get_prime_nums(start,end): prime_nums = 0 for num in range(start,end): if is_prime(num): prime_nums = prime_nums + 1 return prime_nums print(get_prime_nums(2,50000)) # 效果是一样的

1|2码农高天说

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

  • 装饰器decorator:是一个输入是函数,输出也是函数的函数(看讲解一中的装饰器)
  • 类装饰器 class decorator,up主说有一定的歧义
    • 可以当做装饰器的类(装饰器本身)
    • 可以装饰类的装饰器(装饰器要装饰的对象)
  • 装饰器本身既可以是函数也可以是类,装饰的对象同样可以是函数或者类
  • 背这些理论没有意义,关键要弄懂背后的原理
  • __call__可以让类的实例当做函数用(就是callable)

1|3万能公式

  • 装饰器语法糖背后

    class CountTime: ...# 同上 @CountTime def add(a,b): # 就用码农的demo函数 return a+b print(add(1,2))
  • @CountTime等价于,所谓的万能公式

    add = CountTime(add)
  • print(add(1,2))已经不再是使用的原始的add了,用的是新的add

    print(add(1,2)) 等价于 print(CountTime(add)(1,2))
  • 也就是说

    # @CountTime # 去掉装饰器,你就是定义了一个简单的函数 # # add = CountTime(add) # 函数名被重定义了 相当于这样 def add(a,b): return a+b print(CountTime(add)(1,2))
  • 你还可以这样

    def add(a,b): return a+b new_add = CountTime(add) print(new_add(1,2))
  • 是的,被装饰过的函数已经不再是原来的函数了,它总是会先去执行装饰器(CountTime(add))

  • 总结:

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

1|4改造,有参数的装饰器

  • 我们看到过很多的装饰是有参数的,这是怎么实现的呢?

  • 比如你想要输出的信息可以调整其前缀

    计时: 0.46秒 或者 用时: 0.46秒
  • 你希望是这样装饰和调用的

    @CountTime(prefix='用时:') def add(a,b): return a+b print(add(1,2))
  • 那咋实现呢?

  • 回到万能公式:

    @CountTime(prefix='用时:') def add(a,b): ... # 等价于 add = CountTime(add) # 那么 @CountTime(prefix='用时:') def add(a,b): ... # 等价于 add = CountTime(prefix='用时:')(add)
  • CountTime这个类能CountTime(prefix='用时:'),就是实例化做到的,所以...类的init方法要改一下,不再是传参function_name了,而是传你的prefix,像这样

    class CountTimeV2: def __init__(self,prefix='用时:'): self.prefix = prefix
  • 但现在还不能继续,add = CountTime(prefix='用时:')(add)中你(add)还要处理,前面是init做的,()就是callable做的,里面的参数是add,也就是函数的名字,所以你的call也要改造,像这样吗?

    def __call__(self, function_name): from time import time start_time = time() result = function_name(*args,**kwargs) end_time = time() print(f'统计花了{end_time-start_time}时间') return result
  • 不对的,光这样改造不够的,因为你这个function_name(*args,**kwargs)在IDE中就会报错,哪里来的呢?没有定义。

  • 回想讲解一中,函数装饰器里层,还有一个函数,此处就可以参考

    class CountTimeV2: def __init__(self, prefix='用时:'): self.prefix = prefix def __call__(self, function_name): def wrapper(*args, **kwargs): # 加了个函数 , 包裹一层 from time import time start_time = time() result = function_name(*args, **kwargs) # 这样就可以用参数了 end_time = time() print(f'{self.prefix}{end_time - start_time}') # 用之前的定义 return result return wrapper @CountTimeV2(prefix='耗时:') # 可以改为用时、计时等等 def add(a, b): return a + b print(add(1, 2))

前面谈的是类是一个装饰器,装了一个函数

下面谈的是函数是一个装饰器,装饰一个类

1|5类的装饰器

  • 现在有这么一个类

    class Person: pass wuxianfeng = Person() print(wuxianfeng) # <__main__.Person object at 0x000002361C15A460>
  • 你学过python可以这样修改

    class Person: def __str__(self): return f"{self.__class__.__name__}" wuxianfeng = Person() print(wuxianfeng) # Person
  • 但如果有很多的类都要如此呢?

  • 可以写个装饰器,来装饰这些类呗

  • 怎么写?回想刚才你学到的知识,万能公式!

    def show_classname(): # 先不写参数 pass # 先不写内容 @show_classname class Person: pass Person = show_classname(Person) wuxianfeng = Person() print(wuxianfeng)
    • 你现在要写一个函数,名字随意,如show_classname

    • 你肯定要装饰在类上

      @show_classname class Person: pass
    • 根据万能公式,你的Person应该变了

      Person = show_classname(Person) # 从上面这段代码,你要能分析出以下内容 # 1. show_classname应该有个参数,传参是个类名 # 2. 因为可以Person = ,所以show_classname有个返回值
    • 对于使用者而言,应该没有任何操作上的差异

      wuxianfeng = Person() # 从上面这段代码,你要能分析出以下内容 # 1. Person已经被你改变了 # 2. Person()==>show_classname(Person)(),所以show_classname这个函数的返回值还是一个类 print(wuxianfeng)
    • 分析完了,函数体部分是有点不好理解的

      def show_classname(class_name): def __str__(self): return self.__class__.__name__ class_name.__str__ = __str__ return class_name @show_classname class Person: pass Person = show_classname(Person) wuxianfeng = Person() print(wuxianfeng)
    • 看着这个结果,我们来解释下(也许你会更好理解)

      1. show_classname(Person) 返回仍然是Person 2. 但这个时候的Person被改变了一点(你要做的不就是如此吗?) 3. 原来你是这样写的 class Person: def __str__(self): return f"{self.__class__.__name__}" 看看现在的写法 def __str__(self): return self.__class__.__name__ class_name.__str__ = __str__ # 前面的class_name.__str__ 是类自己的函数(本段解释的line 5) # 后面的= __str__ ,是line8的函数 # 是的,函数可以被重新赋值,函数是一等对象,
    • 如果还不明白...尽力了

1|6带参数的类的装饰器

码农高天并没有给出示例代码

当然如果你真懂了前面的"改造,有参数的装饰器",也很简单

  • 直接上代码

    def show_classname(info='类名:'): def wrapper(class_name): def __str__(self): return info+ self.__class__.__name__ class_name.__str__ = __str__ return class_name return wrapper @show_classname('类的名字是:') # class Person: pass wuxianfeng = Person() print(wuxianfeng)
  • 默认值就是='类名:',怎么用呢

    @show_classname() class Human: pass qianyuli = Human() print(qianyuli)
  • 注意不能这样

    @show_classname class Human: pass qianyuli = Human() print(qianyuli)
  • 提示错误

    Traceback (most recent call last): File "demo.py", line 21, in <module> qianyuli = Human() TypeError: wrapper() missing 1 required positional argument: 'class_name'
  • 提个问题,为何会报错?

  • 如果你无法解释的通,你应该还没理解。

  • 答案其实还是万能公式。

    @show_classname class Human: pass # 1. 等价于(万能公式来了) Human = show_classname(Human) # 2. show_classname(Human) 执行这个的时候其实你在做 def show_classname(info='类名:'): ... Human这个东西传给了info # 你要不信,你改为下面这样就知道了;信的话就过 def show_classname(info='类名:'): print('info是啥?',info.name) class Human: name = '女娲' # 3. show_classname(Human)这个的返回是wrapper 但wrapper这个函数是有个参数的,看你的定义def wrapper(class_name): # 4. 定义的时候是感知不到问题的,下面的报错行 qianyuli = Human() 其实你是在 Human()=>show_classname(Human)()=>wrapper(),错了,(看3),你需要一个class_name参数
  • 如果还不明白...尽力了


__EOF__

本文作者博客已废弃
本文链接https://www.cnblogs.com/wuxianfeng023/p/17106513.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   博客已废弃  阅读(200)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示