草庐IT

Python装饰器实例讲解(一)

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

Python装饰器实例讲解(一)

多种角度讲述这个知识,这是个系列文章

但前后未必有一定的顺承关系

部分参考网络

本文以一个小案例引出装饰器的一些特点,不涉及理论,后面再谈

案例

  • 写一个代码来求一个数是否是质数

    def is_prime(x):
        if  x == 2 :
            return True
        elif x == 1 or x % 2 == 0 :
            return False
        for i in range(3, int(x ** 0.5) + 1, 2):
            if x % i == 0:
                return False
        return True
    
  • 写个代码来计算某个数值范围内有多少个质数

    def get_prime_nums():
        return len(list(filter(is_prime,range(2,50000))))
    
  • 换一下,我们不是要学这个,我们要学装饰器

    def get_prime_nums():
        from time import time
        start_time = time()
        prime_nums = 0
        for num in range(2,50000):
            if is_prime(num):
                prime_nums = prime_nums + 1
        end_time = time()
        print(f'统计花了{end_time-start_time}时间')
        print(f'一共有{prime_nums}个质数')
    
    
    get_prime_nums()   
    # 统计花了0.025316476821899414时间
    # 一共有5133个质数
    
    
  • 你在这里会发现一个潜在的需求,可能不光是你这么一个函数有统计时间的需求,其他函数一样有,但现在这种处理方法可能要在每个目标函数上去加那段时间处理的代码,非常麻烦,那有没有好的做法呢?答案就是装饰器。

装饰器

  • 改造(对比下跟之前的区别)

  • 获取质数个数函数,不需要统计时间

    def get_prime_nums():
        prime_nums = 0
        for num in range(2,50000):
            if is_prime(num):
                prime_nums = prime_nums + 1
        print(f'一共有{prime_nums}个质数')
    
  • 写一个装饰器的函数(不用管为何这么写,以后会详细说明)

    def count_time(func):
        def wrapper():
            from time import time
            start_time = time()
            func()
            end_time = time()
            print(f'统计花了{end_time-start_time}时间')
        return wrapper
    
  • 给要加时间的函数套上这个装饰器

    @count_time
    def get_prime_nums():
        prime_nums = 0
        ... # 不重复了
    
  • 再次执行get_prime_nums()效果跟之前是一样的

  • 同样的你可以将这个装饰器运用到其他函数上去

    @count_time
    def get_odd_nums():
        odd_nums = 0
        for num in range(2,50000):
            if num % 2 == 1:
                odd_nums = odd_nums + 1
        print(f'一共有{odd_nums}个奇数')
    
    get_odd_nums()
    
  • 完了吗,没有。可能性还有很多,主要是被装饰函数的变化导致了装饰器本身要随之适应变化。

装饰器改造一

  • 如果被装饰的函数有返回值呢?

    @count_time
    def get_prime_nums():
        prime_nums = 0
        for num in range(2,50000):
            if is_prime(num):
                prime_nums = prime_nums + 1
        return prime_nums
    
  • 此时你直接调用函数,而不改造装饰器的话,是无法得到这个数量的

    get_prime_nums()  # 统计花了0.032898664474487305时间
    
    print(get_prime_nums())
    # 统计花了0.039182424545288086时间
    # None
    
  • 改造装饰器

  • 如何改造呢?你应该要去理解装饰器的运行原理(没那么复杂,但我们这个课不深入,仅作为案例给你展示)

    def count_time(func):
        def wrapper():
            from time import time
            start_time = time()
            result = func()  # 改动1: 用一个变量来接收func()的返回
            end_time = time()
            print(f'统计花了{end_time-start_time}时间')
            return result # 改动2: return出去
        return wrapper
    
  • 此时你再执行

    print(get_prime_nums())
    # 统计花了0.054421424865722656时间
    # 5133 就能看到这个返回值了
    
    
  • 完了吗?还没有,如果我们的被装饰函数有参数呢?

装饰器改造二

  • 你的被装饰函数存在参数

    @count_time
    def get_prime_nums(end):
        prime_nums = 0
        for num in range(2,end):
            if is_prime(num):
                prime_nums = prime_nums + 1
        return prime_nums
    
    
    print(get_prime_nums(50000))
    
  • 其实在IDE中get_prime_nums(50000)就会提示你意外实参

  • 执行结果

    Traceback (most recent call last):
      File "...\demo.py", line 37, in <module>
        print(get_prime_nums(50000))
    TypeError: wrapper() takes 0 positional arguments but 1 was given
    
  • 这是初学者最困惑的地方了,等我们讲了原理(或者说诀窍)你应该就非常清楚为何会这样报错了

  • 怎么修改呢?

    def count_time(func):
        def wrapper(*args): # 改动1: 增加一个不定参数
            from time import time
            start_time = time()
            result = func(*args) # func也增加
            end_time = time()
            print(f'统计花了{end_time-start_time}时间')
            return result
        return wrapper
    
  • 再次执行,就ok了

    print(get_prime_nums(50000)) 
    # 统计花了0.029825448989868164时间
    # 5133
    
  • 但是要注意,这样的话,如果你的被装饰函数是之前的没有参数的情况,是会报错的

    # 回到过去
    @count_time
    def get_prime_nums():
        prime_nums = 0
        for num in range(2,50000):
            if is_prime(num):
                prime_nums = prime_nums + 1
        return prime_nums
    print(get_prime_nums(50000))
    
  • 报错

    Traceback (most recent call last):
      File "...\demo.py", line 37, in <module>
        print(get_prime_nums(50000))
      File "...\demo.py", line 22, in wrapper
        result = func(*args)
    TypeError: get_prime_nums() takes 0 positional arguments but 1 was given
    
    进程已结束,退出代码为 1
    
    
  • 但由于是*args,你改成多个参数倒是可以的

    @count_time
    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))   # 可以执行
    

  • 如果你这样调用

    print(get_prime_nums(start=2,end=50000))
    
  • 报错

    Traceback (most recent call last):
      File "...\demo.py", line 37, in <module>
        print(get_prime_nums(start=2,end=50000))
    TypeError: wrapper() got an unexpected keyword argument 'start'
    
    进程已结束,退出代码为 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装饰器实例讲解(一)的更多相关文章

  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

随机推荐