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 --装饰器内容讲解

    python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能. 3.1 定义装饰器 ...

  2. Python 装饰器实例

    retry 偶然看到一篇文章,想到了前几天的一个需求,git pull性能不稳,需要加入重试机制,正好这个装饰器的实例符合这样的场景. # coding:utf-8 import time impor ...

  3. python --装饰器通俗讲解

    装饰器 什么是装饰器?:在不修改源代码和调用方式的基础上给其增加新的功能,多个装饰器可以装饰在同一个函数上 Python中的装饰器是你进入Python大门的一道坎; 装饰器特点: 不改变原函数原代码: ...

  4. Python装饰器与面向切面编程

    今天来讨论一下装饰器.装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数 ...

  5. Python装饰器讲解

    Python装饰器讲解 定义:本质是函数,就是为其他函数添加附加功能.原则:1.不能修改被装饰的函数的源代码 2.不能修改被装饰的函数的调用方式 import time def timmer(func ...

  6. Python学习:11.Python装饰器讲解(二)

    回顾 上一节我们进行了Python简单装饰器的讲解,但是python的装饰器还有一部分高级的使用方式,这一节就针对python装饰器高级部分进行讲解. 为一个函数添加多个装饰器 今天,老板又交给你一个 ...

  7. Python的装饰器实例用法小结

    这篇文章主要介绍了Python装饰器用法,结合实例形式总结分析了Python常用装饰器的概念.功能.使用方法及相关注意事项 一.装饰器是什么 python的装饰器本质上是一个Python函数,它可以让 ...

  8. 使用python装饰器计算函数运行时间的实例

    使用python装饰器计算函数运行时间的实例 装饰器在python里面有很重要的作用, 如果能够熟练使用,将会大大的提高工作效率 今天就来见识一下 python 装饰器,到底是怎么工作的. 本文主要是 ...

  9. Python 装饰器(Decorators) 超详细分类实例

        Python装饰器分类 Python 装饰器函数: 是指装饰器本身是函数风格的实现; 函数装饰器: 是指被装饰的目标对象是函数;(目标对象); 装饰器类 : 是指装饰器本身是类风格的实现; 类 ...

  10. python装饰器在接口自动化测试中的应用

    在讲解装饰器在接口自动化测试项目的应用之前,我们先来介绍一下python装饰器到底是个什么 装饰器 说装饰器就不得不提一下函数这个一等公民了,在python中函数有几个特性先来了解一下 函数的一些特性 ...

随机推荐

  1. SqlDataAdapter使用小结

    SqlDataAdapter是 DataSet与SQL Server之间的桥接器,用于相互之间的数据操作. 使用方法 1. 通过查询语句 与 SqlConnection对象实现 string strC ...

  2. linux如何修改dns

    #修改dns: [root@iZap201hv2fcgry1alvbznZ ~]# vim /etc/resolv.conf #添加此格式的dns nameserver 114.114.114.114 ...

  3. SpringBoot+hutool工具-数据库数据导出Excel

    hutool文档地址:https://www.hutool.cn/docs/#/ 导入依赖 <dependency> <groupId>cn.hutool</groupI ...

  4. Go | 函数注意事项

    细节汇总 函数的形参列表可以是多个,返回值列表也可以是多个 形参列表和返回值列表的数据类型,可以是值类型.也可以是引用类型 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写表示该函数可以被本 ...

  5. 运用领域模型——DDD

    模型被用来描述人们所关注的现实或想法的某个方面.模型是一种简化.它是对现实的解释 -- 把与解决问题密切相关的方面抽象出来,而忽略无关的细节. 每个软件程序是为了执行用户的某项活动,或是满足客户的某种 ...

  6. express 为所有路由添加 405 method not allowd 响应

    背景知识 HTTP Status Code 405 405 Method not allowed The resource was requested using a method that is n ...

  7. P1314 聪明的质监员(题解)

    题目 小 T 是一名质量监督员,最近负责检验一批矿产的质量.这批矿产共有 \(n\) 个矿石,从 \(1\) 到 \(n\) 逐一编号,每个矿石都有自己的重量 \(w_i\) 以及价值 \(v_i\) ...

  8. 实践GoF的23种设计模式:命令模式

    摘要:命令模式可将请求转换为一个包含与请求相关的所有信息的对象, 它能将请求参数化.延迟执行.实现 Undo / Redo 操作等. 本文分享自华为云社区<[Go实现]实践GoF的23种设计模式 ...

  9. MySql索引下推知识分享

    作者:刘邓忠 Mysql 是大家最常用的数据库,下面为大家带来 mysql 索引下推知识点的分享,以便巩固 mysql 基础知识,如有错误,还请各位大佬们指正. 1 什么是索引下推 索引下推 (Ind ...

  10. 本机无法配置远程服务器上的MySQL ODBC连接驱动

    1.问题描述 我想要访问远程windows服务器上的MySQL数据库,因此需要在本地ODBC驱动上配好远程服务器的MySQL.但配置好基本信息后,测试的时候出现如下问题: 2.解决方法 之所以产生这种 ...