草庐IT

Python装饰器实例讲解(二)

老吴的博客 2023-03-28 原文

Python装饰器实例讲解(二)

Python装饰器实例讲解(一)

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

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

​ 类装饰器

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

案例

  • 代码

    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))  # 效果是一样的
    

码农高天说

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

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

万能公式

  • 装饰器语法糖背后

    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看下即可)

改造,有参数的装饰器

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

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

    计时: 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))
    
    

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

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

类的装饰器

  • 现在有这么一个类

    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的函数
               # 是的,函数可以被重新赋值,函数是一等对象,
      
    • 如果还不明白...尽力了

带参数的类的装饰器

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

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

  • 直接上代码

    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参数
    
    
  • 如果还不明白...尽力了

有关Python装饰器实例讲解(二)的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

    我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

  3. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  4. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  5. ruby-on-rails - RSpec:避免使用允许接收的任何实例 - 2

    我正在处理旧代码的一部分。beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)endRubocop错误如下:Avoidstubbingusing'allow_any_instance_of'我读到了RuboCop::RSpec:AnyInstance我试着像下面那样改变它。由此beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)end对此:let(:sport_

  6. ruby-on-rails - 使用 ruby​​ 将多个实例变量转换为散列的更好方法? - 2

    我收到格式为的回复#我需要将其转换为哈希值(针对活跃商家)。目前我正在遍历变量并执行此操作:response.instance_variables.eachdo|r|my_hash.merge!(r.to_s.delete("@").intern=>response.instance_eval(r.to_s.delete("@")))end这有效,它将生成{:first="charlie",:last=>"kelly"},但它似乎有点hacky和不稳定。有更好的方法吗?编辑:我刚刚意识到我可以使用instance_variable_get作为该等式的第二部分,但这仍然是主要问题。

  7. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  8. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  9. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  10. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

随机推荐