0. 请写下这一节课你学习到的内容:格式不限,回忆并复述是加强记忆的好方式!
这节课我们一起来完成一个类的定制,
基本要求:
- 定制一个计时器的类
- start和stop方法代表启动计时和停止计时
- 假设计时器对象 t1,print(t1) 和直接调用 t1 均显示结果
- 当计时器未启动或已经停止计时,调用stop方法会给予温馨的提示
- 两个计时器对象可以进行相加:t1 + t2
- 只能使用提供的有限资源完成
显然,你需要这些资源:
- 使用time模块的localtime方法获取时间
Python扩展阅读:time 模块详解(时间获取和转换)
- time.localtime 返回 struct_time 的时间格式
你可以用索引值直接去索引它。
- 因为直接调用对象就要显示结果,所以需要表现你的类:__str__ 和 __repr__,具体用法见:Python魔法方法详解-基本的魔法方法
例如:
-
>>> class A:
-
def __str__(self):
-
return("你好,来自江南的你!")
-
>>> a = A()
-
>>> print(a)
-
你好,来自江南的你!
-
>>> a
-
<__main__.A object at 0x000002D0B1F65390>
__str__ 用在被打印的时候需要以字符串的形式输出的时候,就会找到 __str__魔法方法,然后把返回的值打印出来。
下面是__repr__的示例:
-
>>> class B:
-
def __repr__(self):
-
return "你好,来自江南的你!"
-
>>> b = B()
-
>>> b
-
你好,来自江南的你!
下面代码走起:
-
import time as t
-
class MyTimer:
-
def __init__(self):
-
self.unit = ['年', '月', '天', '小时', '分钟', '秒']
-
self.prompt = "未开始计时!"
-
self.lasted = []
-
self.begin = 0
-
self.end = 0
-
def __str__(self):
-
return self.prompt
-
__repr__ = __str__
-
def __add__(self, other):
-
prompt = "总共运行了"
-
result = []
-
for index in range(6):
-
result.append(self.lasted[index] + other.lasted[index])
-
if result[index]:
-
prompt += (str(result[index]) + self.unit[index])
-
return prompt
-
# 开始计时
-
def start(self):
-
self.begin = t.localtime()
-
self.prompt = "提示:请先调用 stop() 停止计时!"
-
print("计时开始...")
-
# 停止计时
-
def stop(self):
-
if not self.begin:
-
print("提示:请先调用 start() 进行计时!")
-
else:
-
self.end = t.localtime()
-
self._calc()
-
print("计时结束!")
-
# 内部方法,计算运行时间
-
def _calc(self):
-
self.lasted = []
-
self.prompt = "总共运行了"
-
for index in range(6):
-
self.lasted.append(self.end[index] - self.begin[index])
-
if self.lasted[index]:
-
self.prompt += (str(self.lasted[index]) + self.unit[index])
-
# 为下一轮计时初始化变量
-
self.begin = 0
-
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),那按照我们用停止时间减开始时间的计算方式就会出现负数,你应该对此做一些转换。
-
import time as t
-
class MyTimer:
-
def __init__(self):
-
self.unit = ['年', '月', '天', '小时', '分钟', '秒']
-
self.borrow = [0, 12, 31, 24, 60, 60]
-
self.prompt = "未开始计时!"
-
self.lasted = []
-
self.begin = 0
-
self.end = 0
-
def __str__(self):
-
return self.prompt
-
__repr__ = __str__
-
def __add__(self, other):
-
prompt = "总共运行了"
-
result = []
-
for index in range(6):
-
result.append(self.lasted[index] + other.lasted[index])
-
if result[index]:
-
prompt += (str(result[index]) + self.unit[index])
-
return prompt
-
# 开始计时
-
def start(self):
-
self.begin = t.localtime()
-
self.prompt = "提示:请先调用 stop() 停止计时!"
-
print("计时开始...")
-
# 停止计时
-
def stop(self):
-
if not self.begin:
-
print("提示:请先调用 start() 进行计时!")
-
else:
-
self.end = t.localtime()
-
self._calc()
-
print("计时结束!")
-
# 内部方法,计算运行时间
-
def _calc(self):
-
self.lasted = []
-
self.prompt = "总共运行了"
-
for index in range(6):
-
temp = self.end[index] - self.begin[index]
-
# 低位不够减,需向高位借位
-
if temp < 0:
-
# 测试高位是否有得“借”,没得借的话向再高位借......
-
i = 1
-
while self.lasted[index-i] < 1:
-
self.lasted[index-i] += self.borrow[index-i] - 1
-
self.lasted[index-i-1] -= 1
-
i += 1
-
self.lasted.append(self.borrow[index] + temp)
-
self.lasted[index-1] -= 1
-
else:
-
self.lasted.append(temp)
-
# 由于高位随时会被借位,所以打印要放在最后
-
for index in range(6):
-
if self.lasted[index]:
-
self.prompt += str(self.lasted[index]) + self.unit[index]
-
# 为下一轮计时初始化变量
-
self.begin = 0
-
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())。
-
import time as t
-
class MyTimer:
-
def __init__(self):
-
self.prompt = "未开始计时!"
-
self.lasted = 0.0
-
self.begin = 0
-
self.end = 0
-
self.default_timer = t.perf_counter
-
def __str__(self):
-
return self.prompt
-
__repr__ = __str__
-
def __add__(self, other):
-
result = self.lasted + other.lasted
-
prompt = "总共运行了 %0.2f 秒" % result
-
return prompt
-
# 开始计时
-
def start(self):
-
self.begin = self.default_timer()
-
self.prompt = "提示:请先调用 stop() 停止计时!"
-
print("计时开始...")
-
# 停止计时
-
def stop(self):
-
if not self.begin:
-
print("提示:请先调用 start() 进行计时!")
-
else:
-
self.end = self.default_timer()
-
self._calc()
-
print("计时结束!")
-
# 内部方法,计算运行时间
-
def _calc(self):
-
self.lasted = self.end - self.begin
-
self.prompt = "总共运行了 %0.2f 秒" % self.lasted
-
# 为下一轮计时初始化变量
-
self.begin = 0
-
self.end = 0
-
# 设置计时器(time.perf_counter() 或 time.process_time())
-
def set_timer(self, timer):
-
if timer == 'process_time':
-
self.default_timer = t.process_time
-
elif timer == 'perf_counter':
-
self.default_timer = t.perf_counter
-
else:
-
print("输入无效,请输入 perf_counter 或 process_time")
2. 既然咱都做到了这一步,那不如再深入一下。再次改进我们的代码,让它能够统计一个函数运行若干次的时间。
要求一:函数调用的次数可以设置(默认是 1000000 次)
要求二:新增一个 timing() 方法,用于启动计时器
函数演示:
-
>>> ================================ RESTART ================================
-
>>>
-
>>> def test():
-
text = "I love FishC.com!"
-
char = 'o'
-
if char in text:
-
pass
-
>>> t1 = MyTimer(test)
-
>>> t1.timing()
-
>>> t1
-
总共运行了 0.27 秒
-
>>> t2 = MyTimer(test, 100000000)
-
>>> t2.timing()
-
>>> t2
-
总共运行了 25.92 秒
-
>>> t1 + t2
-
'总共运行了 26.19 秒'
代码清单:
-
import time as t
-
class MyTimer:
-
def __init__(self, func, number=1000000):
-
self.prompt = "未开始计时!"
-
self.lasted = 0.0
-
self.default_timer = t.perf_counter
-
self.func = func
-
self.number = number
-
def __str__(self):
-
return self.prompt
-
__repr__ = __str__
-
def __add__(self, other):
-
result = self.lasted + other.lasted
-
prompt = "总共运行了 %0.2f 秒" % result
-
return prompt
-
# 内部方法,计算运行时间
-
def timing(self):
-
self.begin = self.default_timer()
-
for i in range(self.number):
-
self.func()
-
self.end = self.default_timer()
-
self.lasted = self.end - self.begin
-
self.prompt = "总共运行了 %0.2f 秒" % self.lasted
-
# 设置计时器(time.perf_counter() 或 time.process_time())
-
def set_timer(self, timer):
-
if timer == 'process_time':
-
self.default_timer = t.process_time
-
elif timer == 'perf_counter':
-
self.default_timer = t.perf_counter
-
else:
-
print("输入无效,请输入 perf_counter 或 process_time")
学习完这一节课后,我想告诉大家一件事,其实,关于 Python 代码优化你需要知道的最重要问题是,决不要自己编写计时函数!!!!!
为一个很短的代码计时都很复杂,因为你不知道处理器有多少时间用于运行这个代码?有什么在后台运行?小小的疏忽可能破坏你的百年大计,后台服务偶尔被 “唤醒” 在最后千分之一秒做一些像查收信件,连接计时通信服务器,检查应用程序更新,扫描病毒,查看是否有磁盘被插入光驱之类很有意义的事。在开始计时测试之前,把一切都关掉,断开网络的连接。再次确定一切都关上后关掉那些不断查看网络是否恢复的服务等等。
接下来是计时框架本身引入的变化因素。Python 解释器是否缓存了方法名的查找?是否缓存代码块的编译结果?正则表达式呢? 你的代码重复运行时有副作用吗?不要忘记,你的工作结果将以比秒更小的单位呈现,你的计时框架中的小错误将会带来不可挽回的结果扭曲。
Python 社区有句俗语:“Python 自己带着电池。” 别自己写计时框架。Python 具备一个叫做 timeit 的完美计时工具。
或许你现在怨恨我为什么不早点说,但如果你在这节课的折腾中已经掌握了类的定制和使用,那我的目的就达到了。接下来,请认真阅读更为专业的计时器用法及实现源码:Python扩展阅读:timeit 模块详解(准确测量小段代码的执行时间),