翼度科技»论坛 编程开发 python 查看内容

Python装饰器实例讲解(一)

3

主题

3

帖子

9

积分

新手上路

Rank: 1

积分
9
Python装饰器实例讲解(一)

多种角度讲述这个知识,这是个系列文章
但前后未必有一定的顺承关系
部分参考网络
本文以一个小案例引出装饰器的一些特点,不涉及理论,后面再谈
案例


  • 写一个代码来求一个数是否是质数
    1. def is_prime(x):
    2.     if  x == 2 :
    3.         return True
    4.     elif x == 1 or x % 2 == 0 :
    5.         return False
    6.     for i in range(3, int(x ** 0.5) + 1, 2):
    7.         if x % i == 0:
    8.             return False
    9.     return True
    复制代码
  • 写个代码来计算某个数值范围内有多少个质数
    1. def get_prime_nums():
    2.     return len(list(filter(is_prime,range(2,50000))))
    复制代码
  • 换一下,我们不是要学这个,我们要学装饰器
    1. def get_prime_nums():
    2.     from time import time
    3.     start_time = time()
    4.     prime_nums = 0
    5.     for num in range(2,50000):
    6.         if is_prime(num):
    7.             prime_nums = prime_nums + 1
    8.     end_time = time()
    9.     print(f'统计花了{end_time-start_time}时间')
    10.     print(f'一共有{prime_nums}个质数')
    11. get_prime_nums()   
    12. # 统计花了0.025316476821899414时间
    13. # 一共有5133个质数
    复制代码
  • 你在这里会发现一个潜在的需求,可能不光是你这么一个函数有统计时间的需求,其他函数一样有,但现在这种处理方法可能要在每个目标函数上去加那段时间处理的代码,非常麻烦,那有没有好的做法呢?答案就是装饰器。
装饰器


  • 改造(对比下跟之前的区别)
  • 获取质数个数函数,不需要统计时间
    1. def get_prime_nums():
    2.     prime_nums = 0
    3.     for num in range(2,50000):
    4.         if is_prime(num):
    5.             prime_nums = prime_nums + 1
    6.     print(f'一共有{prime_nums}个质数')
    复制代码
  • 写一个装饰器的函数(不用管为何这么写,以后会详细说明)
    1. def count_time(func):
    2.     def wrapper():
    3.         from time import time
    4.         start_time = time()
    5.         func()
    6.         end_time = time()
    7.         print(f'统计花了{end_time-start_time}时间')
    8.     return wrapper
    复制代码
  • 给要加时间的函数套上这个装饰器
    1. @count_time
    2. def get_prime_nums():
    3.     prime_nums = 0
    4.     ... # 不重复了
    复制代码
  • 再次执行get_prime_nums()效果跟之前是一样的
  • 同样的你可以将这个装饰器运用到其他函数上去
    1. @count_time
    2. def get_odd_nums():
    3.     odd_nums = 0
    4.     for num in range(2,50000):
    5.         if num % 2 == 1:
    6.             odd_nums = odd_nums + 1
    7.     print(f'一共有{odd_nums}个奇数')
    8. get_odd_nums()
    复制代码
  • 完了吗,没有。可能性还有很多,主要是被装饰函数的变化导致了装饰器本身要随之适应变化。
装饰器改造一


  • 如果被装饰的函数有返回值呢?
    1. @count_time
    2. def get_prime_nums():
    3.     prime_nums = 0
    4.     for num in range(2,50000):
    5.         if is_prime(num):
    6.             prime_nums = prime_nums + 1
    7.     return prime_nums
    复制代码
  • 此时你直接调用函数,而不改造装饰器的话,是无法得到这个数量的
    1. get_prime_nums()  # 统计花了0.032898664474487305时间
    2. print(get_prime_nums())
    3. # 统计花了0.039182424545288086时间
    4. # None
    复制代码
  • 改造装饰器
  • 如何改造呢?你应该要去理解装饰器的运行原理(没那么复杂,但我们这个课不深入,仅作为案例给你展示)
    1. def count_time(func):
    2.     def wrapper():
    3.         from time import time
    4.         start_time = time()
    5.         result = func()  # 改动1: 用一个变量来接收func()的返回
    6.         end_time = time()
    7.         print(f'统计花了{end_time-start_time}时间')
    8.         return result # 改动2: return出去
    9.     return wrapper
    复制代码
  • 此时你再执行
    1. print(get_prime_nums())
    2. # 统计花了0.054421424865722656时间
    3. # 5133 就能看到这个返回值了
    复制代码
  • 完了吗?还没有,如果我们的被装饰函数有参数呢?
装饰器改造二


  • 你的被装饰函数存在参数
    1. @count_time
    2. def get_prime_nums(end):
    3.     prime_nums = 0
    4.     for num in range(2,end):
    5.         if is_prime(num):
    6.             prime_nums = prime_nums + 1
    7.     return prime_nums
    8. print(get_prime_nums(50000))
    复制代码
  • 其实在IDE中get_prime_nums(50000)就会提示你意外实参
  • 执行结果
    1. Traceback (most recent call last):
    2.   File "...\demo.py", line 37, in <module>
    3.     print(get_prime_nums(50000))
    4. TypeError: wrapper() takes 0 positional arguments but 1 was given
    复制代码
  • 这是初学者最困惑的地方了,等我们讲了原理(或者说诀窍)你应该就非常清楚为何会这样报错了
  • 怎么修改呢?
    1. def count_time(func):
    2.     def wrapper(*args): # 改动1: 增加一个不定参数
    3.         from time import time
    4.         start_time = time()
    5.         result = func(*args) # func也增加
    6.         end_time = time()
    7.         print(f'统计花了{end_time-start_time}时间')
    8.         return result
    9.     return wrapper
    复制代码
  • 再次执行,就ok了
    1. print(get_prime_nums(50000))
    2. # 统计花了0.029825448989868164时间
    3. # 5133
    复制代码
  • 但是要注意,这样的话,如果你的被装饰函数是之前的没有参数的情况,是会报错的
    1. # 回到过去@count_time
    2. def get_prime_nums():
    3.     prime_nums = 0
    4.     for num in range(2,50000):
    5.         if is_prime(num):
    6.             prime_nums = prime_nums + 1
    7.     return prime_numsprint(get_prime_nums(50000))
    复制代码
  • 报错
    1. Traceback (most recent call last):
    2.   File "...\demo.py", line 37, in <module>
    3.     print(get_prime_nums(50000))
    4.   File "...\demo.py", line 22, in wrapper
    5.     result = func(*args)
    6. TypeError: get_prime_nums() takes 0 positional arguments but 1 was given
    7. 进程已结束,退出代码为 1
    复制代码
  • 但由于是*args,你改成多个参数倒是可以的
    1. @count_time
    2. def get_prime_nums(start,end):
    3.     prime_nums = 0
    4.     for num in range(start,end):
    5.         if is_prime(num):
    6.             prime_nums = prime_nums + 1
    7.     return prime_nums
    8. print(get_prime_nums(2,50000))   # 可以执行
    复制代码


  • 如果你这样调用
    1. print(get_prime_nums(start=2,end=50000))
    复制代码
  • 报错
    1. Traceback (most recent call last):
    2.   File "...\demo.py", line 37, in <module>
    3.     print(get_prime_nums(start=2,end=50000))
    4. TypeError: wrapper() got an unexpected keyword argument 'start'
    5. 进程已结束,退出代码为 1
    复制代码
  • 可以这样修改你的装饰器
    1. def count_time(func):
    2.     def wrapper(*args,**kwargs):  # 加个关键字参数
    3.         from time import time
    4.         start_time = time()
    5.         result = func(*args,**kwargs) # 这样也要加
    6.         end_time = time()
    7.         print(f'统计花了{end_time-start_time}时间')
    8.         return result
    9.     return wrapper
    复制代码
说在最后


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

来源:https://www.cnblogs.com/wuxianfeng023/p/17104664.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具