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

news/2024/11/29 4:04:49/

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 模块详解(准确测量小段代码的执行时间),


http://www.ppmy.cn/news/957395.html

相关文章

ChatGPT|如何通过ChatGPT问一本书的问题?

很多场景下需要私域数据&#xff0c;但是在使用ChatGPT对话回答是很泛或者没有相关答案&#xff0c;因此你就需要自己喂养数据&#xff0c;然后形成自己的私域数据数据集&#xff0c;以下就是用一本书作为例子&#xff0c;通过输入一本书问ChatGPT关于这本书其中的问题。其步骤…

数学上的问题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 问题合集一、问题&#xff1a;为什么stats.norm.pdf计算出的概率分布值会大于11.代码2.分析 问题合集 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学…

算法中的一些数学问题分享,ICG游戏

1&#xff0c;石子游戏 题目出处 https://leetcode-cn.com/problems/stone-game/ 亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行&#xff0c;每堆都有正整数颗石子 piles[i] 。 游戏以谁手中的石子最多来决出胜负。石子的总数是奇数&#xff0c;所以没有平局。 亚历克…

力扣 135. 分发糖果

题目来源&#xff1a;https://leetcode.cn/problems/candy/description/ C题解&#xff08;来源代码随想录&#xff09;&#xff1a; 先从左往右比较&#xff0c;右边孩子评分比左边高就多发1颗糖&#xff0c;否则就只发1颗&#xff1b;再从右往左比较&#xff0c;左边孩子评分…

C# delegate,Action,Func,Event 使用

文章目录 1. delegate 委托2. Event 事件3. Action 无返回值委托4. Func 带返回值委托 1. delegate 委托 作用&#xff1a; &#xff08;1&#xff09;将方法当作参数传递 &#xff08;2&#xff09;方法的一种多态&#xff08;类似于一个方法模板&#xff0c;可以匹配很多个方…

python 笔记:you-get

下载视频/音乐/图片 使用pip安装you-get pip3 install you-get 之后在命令行执行下载操作 1 主要命令行参数 -n --no-merge 如果视频分p&#xff0c;不进行合并--no-caption不下载弹幕、歌词等 -f --force 覆盖已存在的文件 -F STREAM_ID --format STREAM_ID 指定视频下载…

【产品设计】通用后台管理系统需求及原型设计

后台管理系统&#xff0c;会根据不同公司、不同业务的要求做出改变。 网上很多系统的参考多数为业务中台&#xff0c;过于带有业务色彩。做过三四个后台管理系统&#xff0c;从中总结了一个通用的功能和需求设计模版&#xff0c;供大家参考。本文适用于0-2岁的产品经理做基础功…

JS-26 认识防抖和节流函数;自定义防抖、节流函数;自定义深拷贝、事件总线函数

目录 1_防抖和节流1.1_认识防抖和节流函数1.2_认识防抖debounce函数1.3_防抖函数的案例1.4_认识节流throttle函数 2_Underscore实现防抖和节流2.1_Underscore实现防抖和节流2.2_自定义防抖函数2.3_自定义节流函数 3_自定义深拷贝函数4_自定义事件总线 1_防抖和节流 1.1_认识防…