目录
请写下这一节课你学习到的内容:格式不限,回忆并复述是加强记忆的好方式!
这节课我们一起来完成一个类的定制,
基本要求:
- 定制一个计时器的类
- 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("1!")
>>> a = A()
>>> print(a)
1!
>>> a
<__main__.A object at 0x000002D0B1F65390>
str 用在被打印的时候需要以字符串的形式输出的时候,就会找到 __str__魔法方法,然后把返回的值打印出来。
下面是__repr__的示例:
>>> class B:
def __repr__(self):
return "1!"
>>> b = B()
>>> b
1!
定义一个计时器的类
import time as t
class MyTimer:
def __init__(self):
self.unit = ['年', '月', '天', '小时', '分钟', '秒']
self.count=[0,12,30,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):
self.lasted.append(self.end[index] - self.begin[index])
'''
if self.lasted[index]:
self.prompt += (str(self.lasted[index]) + self.unit[index])'''
printTime(self.lasted)
# 为下一轮计时初始化变量
self.begin = 0
self.end = 0
def printTime(time_list=[]):
unit = ['年', '月', '天', '小时', '分钟', '秒']
count=[0,12,30,24,60,60]
for i in range(1,7):
i=-1*i
if time_list[i]<0:
time_list[i]+=count[i]
time_list[i-1]-=1
strtime=''
for i in range(6):
if i:
strtime+=str(time_list[i])+unit[i]
print(strtime)
进阶定制
- 如果开始计时的时间是(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.count=[0,12,30,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):
self.lasted.append(self.end[index] - self.begin[index])
'''
if self.lasted[index]:
self.prompt += (str(self.lasted[index]) + self.unit[index])'''
printTime(self.lasted)
# 为下一轮计时初始化变量
self.begin = 0
self.end = 0
def printTime(time_list=[]):
unit = ['年', '月', '天', '小时', '分钟', '秒']
count=[0,12,30,24,60,60]
for i in range(1,7):
i=-1*i
if time_list[i]<0:
time_list[i]+=count[i]
time_list[i-1]-=1
strtime=''
for i in range(6):
if i:
strtime+=str(time_list[i])+unit[i]
print(strtime)
相信大家已经意识到不对劲了:为毛一个月一定要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 Timer:
def __init__(self):
self.prompt = "未开始计时!"
self.begin = 0
self.end = 0
self.default_timer = t.perf_counter
self.dt=0
def __str__(self):
return self.prompt
__repr__ = __str__
def __add__(self, other):
prompt = "总共运行了"
prompt+=str(self.dt+ other.dt)+'秒'
return prompt
# 开始计时
def start(self):
self.begin = t.perf_counter()
self.prompt = "提示:请先调用 stop() 停止计时!"
print("计时开始...")
# 停止计时
def stop(self):
if not self.begin:
print("提示:请先调用 start() 进行计时!")
else:
self.end = t.perf_counter()
self.dt=self.end-self.begin
self._calc()
print("计时结束!")
# 内部方法,计算运行时间
def _calc(self):
self.prompt = "总共运行了"
self.prompt+=str(self.dt)+'秒'
# 为下一轮计时初始化变量
self.begin = 0
self.end = 0
def set_Timer(self,timer):#设置一个计时器,即选process或者perf
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 #设置perf为默认计时器
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
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 模块详解(准确测量小段代码的执行时间)