《零基础入门学习Python》第044讲:魔法方法:简单定制

本节课讲述了如何在Python中定制一个计时器类,包括start和stop方法,以及支持对象相加。类设计涉及到了__str__和__repr__方法的使用,以及time模块的time.localtime。此外,还讨论了使用timeit模块进行精确的代码执行时间测量的重要性,强调了不应自行编写计时函数。
摘要由CSDN通过智能技术生成

0. 请写下这一节课你学习到的内容:格式不限,回忆并复述是加强记忆的好方式!

这节课我们一起来完成一个类的定制,

基本要求:

  • 定制一个计时器的类
  • start和stop方法代表启动计时和停止计时
  • 假设计时器对象 t1,print(t1) 和直接调用 t1 均显示结果
  • 当计时器未启动或已经停止计时,调用stop方法会给予温馨的提示
  • 两个计时器对象可以进行相加:t1 + t2
  • 只能使用提供的有限资源完成

显然,你需要这些资源:

  • 使用time模块的localtime方法获取时间

Python扩展阅读:time 模块详解(时间获取和转换)

  • time.localtime 返回 struct_time 的时间格式

你可以用索引值直接去索引它。

  • 因为直接调用对象就要显示结果,所以需要表现你的类:__str__ 和 __repr__,具体用法见:Python魔法方法详解-基本的魔法方法

例如:

 
  1. >>> class A:

  2. def __str__(self):

  3. return("你好,来自江南的你!")

  4. >>> a = A()

  5. >>> print(a)

  6. 你好,来自江南的你!

  7. >>> a

  8. <__main__.A object at 0x000002D0B1F65390>

__str__ 用在被打印的时候需要以字符串的形式输出的时候,就会找到 __str__魔法方法,然后把返回的值打印出来。

下面是__repr__的示例:

 
  1. >>> class B:

  2. def __repr__(self):

  3. return "你好,来自江南的你!"

  4. >>> b = B()

  5. >>> b

  6. 你好,来自江南的你!

下面代码走起:

 
  1. import time as t

  2. class MyTimer:

  3. def __init__(self):

  4. self.unit = ['年', '月', '天', '小时', '分钟', '秒']

  5. self.prompt = "未开始计时!"

  6. self.lasted = []

  7. self.begin = 0

  8. self.end = 0

  9. def __str__(self):

  10. return self.prompt

  11. __repr__ = __str__

  12. def __add__(self, other):

  13. prompt = "总共运行了"

  14. result = []

  15. for index in range(6):

  16. result.append(self.lasted[index] + other.lasted[index])

  17. if result[index]:

  18. prompt += (str(result[index]) + self.unit[index])

  19. return prompt

  20. # 开始计时

  21. def start(self):

  22. self.begin = t.localtime()

  23. self.prompt = "提示:请先调用 stop() 停止计时!"

  24. print("计时开始...")

  25. # 停止计时

  26. def stop(self):

  27. if not self.begin:

  28. print("提示:请先调用 start() 进行计时!")

  29. else:

  30. self.end = t.localtime()

  31. self._calc()

  32. print("计时结束!")

  33. # 内部方法,计算运行时间

  34. def _calc(self):

  35. self.lasted = []

  36. self.prompt = "总共运行了"

  37. for index in range(6):

  38. self.lasted.append(self.end[index] - self.begin[index])

  39. if self.lasted[index]:

  40. self.prompt += (str(self.lasted[index]) + self.unit[index])

  41. # 为下一轮计时初始化变量

  42. self.begin = 0

  43. self.end = 0

进阶定制

  • 如果开始计时的时间是(2022年2月22日16:30:30),停止时间是(2025年1月23日15:30:30),那按照我们用停止时间减开始时间的计算方式就会出现负数(3年-1月1天-1小时),你应该对此做一些转换。
  • 现在的计算机速度都非常快,而我们这个程序最小的计算单位却只是秒,精度是远远不够的。(看下面的课后作业)

动动手

0. 按照课堂中的程序,如果开始计时的时间是(2022年2月22日16:30:30),停止时间是(2025年1月23日15:30:30),那按照我们用停止时间减开始时间的计算方式就会出现负数,你应该对此做一些转换。

 
  1. import time as t

  2. class MyTimer:

  3. def __init__(self):

  4. self.unit = ['年', '月', '天', '小时', '分钟', '秒']

  5. self.borrow = [0, 12, 31, 24, 60, 60]

  6. self.prompt = "未开始计时!"

  7. self.lasted = []

  8. self.begin = 0

  9. self.end = 0

  10. def __str__(self):

  11. return self.prompt

  12. __repr__ = __str__

  13. def __add__(self, other):

  14. prompt = "总共运行了"

  15. result = []

  16. for index in range(6):

  17. result.append(self.lasted[index] + other.lasted[index])

  18. if result[index]:

  19. prompt += (str(result[index]) + self.unit[index])

  20. return prompt

  21. # 开始计时

  22. def start(self):

  23. self.begin = t.localtime()

  24. self.prompt = "提示:请先调用 stop() 停止计时!"

  25. print("计时开始...")

  26. # 停止计时

  27. def stop(self):

  28. if not self.begin:

  29. print("提示:请先调用 start() 进行计时!")

  30. else:

  31. self.end = t.localtime()

  32. self._calc()

  33. print("计时结束!")

  34. # 内部方法,计算运行时间

  35. def _calc(self):

  36. self.lasted = []

  37. self.prompt = "总共运行了"

  38. for index in range(6):

  39. temp = self.end[index] - self.begin[index]

  40. # 低位不够减,需向高位借位

  41. if temp < 0:

  42. # 测试高位是否有得“借”,没得借的话向再高位借......

  43. i = 1

  44. while self.lasted[index-i] < 1:

  45. self.lasted[index-i] += self.borrow[index-i] - 1

  46. self.lasted[index-i-1] -= 1

  47. i += 1

  48. self.lasted.append(self.borrow[index] + temp)

  49. self.lasted[index-1] -= 1

  50. else:

  51. self.lasted.append(temp)

  52. # 由于高位随时会被借位,所以打印要放在最后

  53. for index in range(6):

  54. if self.lasted[index]:

  55. self.prompt += str(self.lasted[index]) + self.unit[index]

  56. # 为下一轮计时初始化变量

  57. self.begin = 0

  58. self.end = 0

1. 相信大家已经意识到不对劲了:为毛一个月一定要31天?不知道有可能也是30天或者29天吗?(上一题我们的答案是假设一个月31天)

没错,如果要正确得到月份的天数,我们还需要考虑是否闰年,还有每月的最大天数,所以太麻烦了……如果我们不及时纠正,我们会在错误的道路上越走越远……

所以,这一次,小甲鱼提出了更优秀的解决方案(Python官方推荐):用 time 模块的 perf_counter() 和 process_time() 来计算,其中 perf_counter() 返回计时器的精准时间(系统的运行时间); process_time() 返回当前进程执行 CPU 的时间总和。

题目:改进我们课堂中的例子,这次使用 perf_counter() 和 process_time() 作为计时器。另外增加一个 set_timer() 方法,用于设置默认计时器(默认是 perf_counter(),可以通过此方法修改为 process_time())。

 
  1. import time as t

  2. class MyTimer:

  3. def __init__(self):

  4. self.prompt = "未开始计时!"

  5. self.lasted = 0.0

  6. self.begin = 0

  7. self.end = 0

  8. self.default_timer = t.perf_counter

  9. def __str__(self):

  10. return self.prompt

  11. __repr__ = __str__

  12. def __add__(self, other):

  13. result = self.lasted + other.lasted

  14. prompt = "总共运行了 %0.2f 秒" % result

  15. return prompt

  16. # 开始计时

  17. def start(self):

  18. self.begin = self.default_timer()

  19. self.prompt = "提示:请先调用 stop() 停止计时!"

  20. print("计时开始...")

  21. # 停止计时

  22. def stop(self):

  23. if not self.begin:

  24. print("提示:请先调用 start() 进行计时!")

  25. else:

  26. self.end = self.default_timer()

  27. self._calc()

  28. print("计时结束!")

  29. # 内部方法,计算运行时间

  30. def _calc(self):

  31. self.lasted = self.end - self.begin

  32. self.prompt = "总共运行了 %0.2f 秒" % self.lasted

  33. # 为下一轮计时初始化变量

  34. self.begin = 0

  35. self.end = 0

  36. # 设置计时器(time.perf_counter() 或 time.process_time())

  37. def set_timer(self, timer):

  38. if timer == 'process_time':

  39. self.default_timer = t.process_time

  40. elif timer == 'perf_counter':

  41. self.default_timer = t.perf_counter

  42. else:

  43. print("输入无效,请输入 perf_counter 或 process_time")

2. 既然咱都做到了这一步,那不如再深入一下。再次改进我们的代码,让它能够统计一个函数运行若干次的时间。

要求一:函数调用的次数可以设置(默认是 1000000 次)
要求二:新增一个 timing() 方法,用于启动计时器
 

函数演示:

 
  1. >>> ================================ RESTART ================================

  2. >>>

  3. >>> def test():

  4. text = "I love FishC.com!"

  5. char = 'o'

  6. if char in text:

  7. pass

  8. >>> t1 = MyTimer(test)

  9. >>> t1.timing()

  10. >>> t1

  11. 总共运行了 0.27 秒

  12. >>> t2 = MyTimer(test, 100000000)

  13. >>> t2.timing()

  14. >>> t2

  15. 总共运行了 25.92 秒

  16. >>> t1 + t2

  17. '总共运行了 26.19 秒'

代码清单:

 
  1. import time as t

  2. class MyTimer:

  3. def __init__(self, func, number=1000000):

  4. self.prompt = "未开始计时!"

  5. self.lasted = 0.0

  6. self.default_timer = t.perf_counter

  7. self.func = func

  8. self.number = number

  9. def __str__(self):

  10. return self.prompt

  11. __repr__ = __str__

  12. def __add__(self, other):

  13. result = self.lasted + other.lasted

  14. prompt = "总共运行了 %0.2f 秒" % result

  15. return prompt

  16. # 内部方法,计算运行时间

  17. def timing(self):

  18. self.begin = self.default_timer()

  19. for i in range(self.number):

  20. self.func()

  21. self.end = self.default_timer()

  22. self.lasted = self.end - self.begin

  23. self.prompt = "总共运行了 %0.2f 秒" % self.lasted

  24. # 设置计时器(time.perf_counter() 或 time.process_time())

  25. def set_timer(self, timer):

  26. if timer == 'process_time':

  27. self.default_timer = t.process_time

  28. elif timer == 'perf_counter':

  29. self.default_timer = t.perf_counter

  30. else:

  31. print("输入无效,请输入 perf_counter 或 process_time")

学习完这一节课后,我想告诉大家一件事,其实,关于 Python 代码优化你需要知道的最重要问题是,决不要自己编写计时函数!!!!!

为一个很短的代码计时都很复杂,因为你不知道处理器有多少时间用于运行这个代码?有什么在后台运行?小小的疏忽可能破坏你的百年大计,后台服务偶尔被 “唤醒” 在最后千分之一秒做一些像查收信件,连接计时通信服务器,检查应用程序更新,扫描病毒,查看是否有磁盘被插入光驱之类很有意义的事。在开始计时测试之前,把一切都关掉,断开网络的连接。再次确定一切都关上后关掉那些不断查看网络是否恢复的服务等等。

接下来是计时框架本身引入的变化因素。Python 解释器是否缓存了方法名的查找?是否缓存代码块的编译结果?正则表达式呢? 你的代码重复运行时有副作用吗?不要忘记,你的工作结果将以比秒更小的单位呈现,你的计时框架中的小错误将会带来不可挽回的结果扭曲。

Python 社区有句俗语:“Python 自己带着电池。” 别自己写计时框架。Python 具备一个叫做 timeit 的完美计时工具。

或许你现在怨恨我为什么不早点说,但如果你在这节课的折腾中已经掌握了类的定制和使用,那我的目的就达到了。接下来,请认真阅读更为专业的计时器用法及实现源码:Python扩展阅读:timeit 模块详解(准确测量小段代码的执行时间),

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值