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

说在最后

  • 这个案例是入门的,讲解了装饰器的一些简单使用
  • 但,留了一些坑,你可能未必知道为何要这么修改,装饰器是怎么调度的等等
  • 且听下回分解

最新文章

  1. tachyon 集群安装
  2. 【IOCP】 IOCP模型属于一种通讯模型- 较难
  3. Windows下配置使用 MemCached
  4. jquery判断输入文字个数的统计代码
  5. Html笔记(五)表格
  6. Cookie中用户登录信息的提示
  7. Java之进程与线程练习
  8. .Net Core 图片文件上传下载
  9. windows10企业版怎么关闭自动更新
  10. 我修改的时钟flash
  11. Yii2如何添加sql日志记录的配置信息
  12. 小白学PYTHON时最容易犯的6个错误,看看你遇到过几个
  13. android 6.0 Intent 安装apk闪退
  14. Rsync客户端卡死的问题查询
  15. kernel笔记——库文件与系统调用
  16. Linux命令:let
  17. Lucene入门学习二
  18. 通过webservice(System.Data.OracleClient)调试oracle
  19. 【SE】Week2 : 个人博客作业
  20. CF1096D Easy Problem(DP)

热门文章

  1. vue 数组更新(push【可用】,$set,slice,filter,map【都属于浅拷贝】)问题
  2. 嵌入式-C语言基础:联合体和共用体的概念
  3. Java 同步锁ReentrantLock与抽象同步队列AQS
  4. c++小练习——黑白棋
  5. C++ 一个简洁的CHECK宏
  6. 使用VMware安装Linux(CentOS)操作系统
  7. Selenium4+Python3系列(十一) - Page Factory设计模式
  8. ATM项目
  9. MIT6.828学习笔记3(Lab3)
  10. 使用NPOI core插入图片