第044讲:魔法方法:简单定制
课题笔记:
这节课完成一个类的定制:
效果:
__str__() 就是用在被打印的时候需要字符串形式输出的时候,就会找到这个魔法方法。然后把它返回的值给打印出来。
>>> class A(): def __str__(self): return "hale" >>> a = A() >>> print(a) hale >>> a <__main__.A object at 0x00000281B66C33A0> >>> class B(): def __repr__(self): return "Hale" >>> b = B() >>> b Hale >>>
所以重写这2个魔法方法就可以做到这些要的功能。
time模块:[扩展阅读] time 模块详解(时间获取和转换)
先试写了,使用pycharm需要设置断点? 但是不知道在哪里设置,如何设置。
import time as t #首先我们需要time的模块,就先导入 class MyTimer(): #开始计时 def start(self): self.star = t.localtime() print('计时开始...') #停止计时 def stop(self): self.stop = t.localtime() self._calc() print('计时结束!') #到此地基写好了,考虑下怎么计算,直接相减? localtime返回的是一个元祖的结构 #内部方法,计算运行时间。 def _calc(self): self.lasted = [] self.prompt = '总共运行了' #这是提示 for index in range(6):#要用前面的6个元素,就是localtime返回的元组结构 self.lasted.append(self.stop[index] - self.start[index]) self.prompt += str(self.lasted[index]) print(self.prompt)
找到错误了,是def start的里面少了个t
改正:
1 import time as t #首先我们需要time的模块,就先导入 2 3 class MyTimer(): 4 #开始计时 5 def start(self): 6 self.start = t.localtime() 7 print('计时开始...') 8 9 #停止计时 10 def stop(self): 11 self.stop = t.localtime() 12 self._calc() 13 print('计时结束!') 14 15 #到此地基写好了,考虑下怎么计算,直接相减? localtime返回的是一个元祖的结构 16 17 #内部方法,计算运行时间。 18 def _calc(self): 19 self.lasted = [] 20 self.prompt = '总共运行了' #这是提示 21 for index in range(6):#要用前面的6个元素,就是localtime返回的元组结构 22 self.lasted.append(self.stop[index] - self.start[index]) 23 self.prompt += str(self.lasted[index]) 24 25 print(self.prompt)
接下来就是加功能,print(t1) 还有直接调用t1它会显示结果,需要重写str和repr这2个魔法方法来实现:
如果这里不按常理出牌,:
>>> t1 = MyTimer() >>> t1 Traceback (most recent call last): File "<pyshell#45>", line 1, in <module> t1 File "C:\Users\1\AppData\Local\Programs\Python\Python38\lib\idlelib\rpc.py", line 620, in displayhook text = repr(value) File "D:/python练习/MyTimer.py", line 5, in __str__ return self.prompt AttributeError: 'MyTimer' object has no attribute 'prompt'
当我们直接调用t1的时候,Python会直接找到这个str的魔法方法,找到repr这个魔法方法,而repr又等于str,所以会找到str方法。str魔法方法里面调用了self.prompt, 那它没定义,它在哪里定义呢?
在calc里面的赋值那,赋值就是定义。那一来就让他显示出来肯定不行,要先通过start 在stop,然后才被赋值,才被定义,才能够使用它。所以所有属于示例对象的变量在init里面先定义就不会出现问题了、。
测试:
t1 = MyTimer() t1.start() Traceback (most recent call last): File "<input>", line 1, in <module> TypeError: 'int' object is not callable
又出错了汗。。。。属性名和方法名重复了!!!!
初始化时候,属性覆盖了方法!错误类型就是整形不能被调用。
改下:
import time as t #首先我们需要time的模块,就先导入 class MyTimer(): def __init__(self): self.prompt = '未开始计时' self.lasted = [] self.begin = 0 self.end = 0 def __str__(self): return self.prompt __repr__ = __str__ #开始计时 def start(self): self.begin = t.localtime() print('计时开始...') #停止计时 def stop(self): self.end = t.localtime() self._calc() print('计时结束!') #到此地基写好了,考虑下怎么计算,直接相减? localtime返回的是一个元祖的结构 #内部方法,计算运行时间。 def _calc(self): self.lasted = [] self.prompt = '总共运行了' #这是提示 for index in range(6):#要用前面的6个元素,就是localtime返回的元组结构 self.lasted.append(self.end[index] - self.begin[index]) self.prompt += str(self.lasted[index])
接下来就是显示的结果问题,显示000002不合适,我们希望显示年月日时分秒,值为0就不显示。
我们添加一个列表用来存放对应的单位,只显示有数据的单位
1 import time as t #首先我们需要time的模块,就先导入 2 3 class MyTimer(): 4 def __init__(self): 5 self.unit = ['年', '月', '日', '小时', '分钟', '秒'] 6 self.prompt = '未开始计时' 7 self.lasted = [] 8 self.begin = 0 9 self.end = 0 10 def __str__(self): 11 return self.prompt 12 13 __repr__ = __str__ 14 15 #开始计时 16 def start(self): 17 self.begin = t.localtime() 18 print('计时开始...') 19 20 #停止计时 21 def stop(self): 22 self.end = t.localtime() 23 self._calc() 24 print('计时结束!') 25 26 #到此地基写好了,考虑下怎么计算,直接相减? localtime返回的是一个元祖的结构 27 28 #内部方法,计算运行时间。 29 def _calc(self): 30 self.lasted = [] 31 self.prompt = '总共运行了' #这是提示 32 for index in range(6):#要用前面的6个元素,就是localtime返回的元组结构 33 self.lasted.append(self.end[index] - self.begin[index]) 34 if self.lasted[index] : #这边只显示索引不为0的,也就是显示有数值的单位 35 self.prompt += (str(self.lasted[index]) + self.unit[index])
需要在适当的地方添加一些温馨提示,例如:还没有开始你就点击了stop,提示先运行start开始计时。
import time as t #首先我们需要time的模块,就先导入 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 start(self): self.begin = t.localtime() print('计时开始...') self.prompt = '提示:请先调用stop()停止计时!' #停止计时 def stop(self): if not self.begin: print('请先调用start()进行计时!') else: self.end = t.localtime() self._calc() print('计时结束!') #到此地基写好了,考虑下怎么计算,直接相减? localtime返回的是一个元祖的结构 #内部方法,计算运行时间。 def _calc(self): self.lasted = [] self.prompt = '总共运行了' #这是提示 for index in range(6):#要用前面的6个元素,就是localtime返回的元组结构 self.lasted.append(self.end[index] - self.begin[index]) if self.lasted[index] : #这边只显示索引不为0的,也就是显示有数值的单位 self.prompt += (str(self.lasted[index]) + self.unit[index]) #为下一轮计时初始化变量 self.begin = 0 self.end = 0
结果:
>>> runfile('D:/python练习/MyTimer.py', wdir='D:/python练习') t1 = MyTimer() t1 未开始计时 t1.stop() 请先调用start()进行计时! t1.start() 计时开始... t1 提示:请先调用stop()停止计时! t1.stop() 计时结束! t1 总共运行了8秒
最后重写一下add魔法方法,让2个计时器的对象相加会自动返回他们的时间和。
import time as t #首先我们需要time的模块,就先导入 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 = "总共运行了" #这里没有self! 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() print('计时开始...') self.prompt = '提示:请先调用stop()停止计时!' #停止计时 def stop(self): if not self.begin: print('请先调用start()进行计时!') else: self.end = t.localtime() self._calc() print('计时结束!') #到此地基写好了,考虑下怎么计算,直接相减? localtime返回的是一个元祖的结构 #内部方法,计算运行时间。 def _calc(self): self.lasted = [] self.prompt = '总共运行了' #这是提示 for index in range(6):#要用前面的6个元素,就是localtime返回的元组结构 self.lasted.append(self.end[index] - self.begin[index]) if self.lasted[index] : #这边只显示索引不为0的,也就是显示有数值的单位 self.prompt += (str(self.lasted[index]) + self.unit[index]) #为下一轮计时初始化变量 self.begin = 0 self.end = 0
结果:
t1 = MyTimer() t1.start() 计时开始... t1.stop() 计时结束! t1 总共运行了4秒 t2 = MyTimer() t2.start() 计时开始... t2.stop() 计时结束! t2 总共运行了4秒 t1 + t2 '总共运行了8秒'
有1分钟-30秒是什么意思,
动动手:
0. 按照课堂中的程序,如果开始计时的时间是(2022年2月22日16:30:30),停止时间是(2025年1月23日15:30:30),那按照我们用停止时间减开始时间的计算方式就会出现负数,你应该对此做一些转换。
思路:全部转化为秒吗? 我的做法是加一个数组,
有负数出现就补充相应的一整个单位,且前一个时间单位减1.
答:参考代码写的比较“纠结”,期待鱼油们写出更漂亮的实现。
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 #默认初始化的计时器为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): 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 的完美计时工具。
或许你现在怨恨小甲鱼为什么不早点说,但如果你在这节课的折腾中已经掌握了类的定制和使用,那小甲鱼的目的就达到了。接下来,请认真阅读更为专业的计时器用法及实现源码:【扩展阅读】timeit 模块详解(准确测量小段代码的执行时间)
作者:Agiroy_70
本博客所有文章仅用于学习、研究和交流目的,欢迎非商业性质转载。
博主的文章主要是记录一些学习笔记、作业等。文章来源也已表明,由于博主的水平不高,不足和错误之处在所难免,希望大家能够批评指出。
博主是利用读书、参考、引用、抄袭、复制和粘贴等多种方式打造成自己的文章,请原谅博主成为一个无耻的文档搬运工!