小甲鱼第45课 魔术方法 简单定制

  • 1.课后作业
  • 1.1 作业0
  • 1.1.1题目理解
  • 1.1.2 一点小修改
  • 1.1.3 代码1
  • 1.1.4 思考和扩展
  • 1.2 作业1
  • 1.2.1 时间的正确输出[^2]
  • 1.2.2 代码2
  • 1.2.3 秒改成天(日期输出)
  • 参考资料


关于小甲鱼《零基础入门学习 python》第45课 魔术方法 简单定制 的一点小思考1

基础代码在小甲鱼的视频里有讲解,我就不浪费时间了
下面是B站链接,这是小甲鱼大佬自己上传的老版视频,现在fishc论坛应该有最新版的喜欢的可以去支持一下
https://www.bilibili.com/video/BV1Fs411A7HZ?p=45

1.课后作业

1.1 作业0

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

1.1.1题目理解

最主要就是实现
start()、
shop()、
时间计算、
正确输出最终用时

1.1.2 一点小修改

我也是初学者所以讲不了什么模块、什么方法的,以下这只是我的思路分析,大家看看就好。
我在做这个小程序时,一开始也是一头蒙,以前c php java 都了解一点皮毛,就用java的思路做个类,new一下,再做个函数调用什么的。但这是python啊,这么麻烦还学个锤子。于是好好看着视频,跟着理了一遍思路,可能感觉是作弊,但是这个程序做下来,我确实对python编程思路有了大致的理解,不会再被之前思路限制了(我不会说这是之前课后作业没好好做的后果)

好了,接下来书归正传

看到小甲鱼的代码,自己理解之后,我的反应是 还有什么可以提升
就是找bug
#问题一:连续两个start()相当于重新计时(已解决)
#问题二: 当前位不够减, 可能出现负数 (解决)
#问题三: 如何更加精确(在1.2的代码里会有)
ps:个人感觉对于刚接触的新生事物,人们都有最大好奇心,就像是刚开始认知世界的小孩子一样,这个时候人的思维最发散,最跳跃,也最不容易被条条框框限制,我一般在这个时候回去想一些发散性问题。问题不可怕,想办法解决就是了。

1.1.3 代码1

各位看官如果是来对比代码的就先看代码,再上解决思路,也可转到1.1.4先看思路

import time as myti
import math as mymath

class MyTimer:                    #问题3 如何更加精确
    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):                    #问题1 连续两个start()相当于重新计时(已解决)
        if self.begin:
            print("计时已经开始,请先调用stop()停止当前计时器")
        else:
            self.begin = myti.localtime() 
            self.prompt = "请先调用stop()停止当前计时器"
            print("计时开始……")
    
    #停止计时
    def stop(self):
        if not self.begin:
            print( "请先调用start()启动一个计时器")
        else:
            self.end = myti.localtime()
            self._calc()  
            print("计时结束")

    
    
     ## #内部方法 计算运行时间
    def _calc (self):
        self.lasted = []
        self.prompt = "总共运行了"
        tag = 0
        temp = []
        
        for index in range(6):  #计算
            rindex = 5 - index

            #是否进位
            if tag:
                self.lasted.append(self.end[rindex] - self.begin[rindex] - 1)
            else  :
                self.lasted.append(self.end[rindex] - self.begin[rindex])
            tag = 0
            
            #取正数
            if self.lasted[index] <0 : 
                if index <= 1:
                    self.lasted[index] = 60 - self.begin[rindex] + self.end[rindex] - 1
                elif index <=2:
                    self.lasted[index] = 24 - self.begin[rindex] + self.end[rindex] - 1
                elif index <=3:
                    self.lasted[index] = 31 - self.begin[rindex] + self.end[rindex] - 1
                else:
                    self.lasted[index] = 12 - self.begin[rindex] + self.end[rindex] - 1

                tag = 1
        
        for index in range(6):  #正序输出
            rindex = 5 - index
            if self.lasted[rindex] :                     #问题2 当前位不够减可能出现负数 (解决)
                self.prompt += (str(self.lasted[rindex]) + self.unit[index])
        
        #为下一轮计时初始化
        self.begin = 0
        self.end = 0
 
''' 
#测试 么有sleep()需要手动输入
t1 = MyTimer()
t1.start()
t1.stop()
t1
t2 = MyTimer()
t2.start()
t2.stop()
#print(t1 + t2)
'''

1.1.4 思考和扩展

对于问题一 两次连续执行start(),

  第二次的时间会覆盖第一次的时间,这是一个不大不小的bug(其实也算不上),我思考的是如果以后真的做成图形化,用户误点击怎么办?

答:很简单,只确认一次就好
方式和stop()的方式相同,就不赘述了

问题二: 当前位不够减, 可能出现负数

小甲鱼参考答案:
(减法思维)是用循环借位的方式,感觉也有些繁琐,
参考答案给的代码是每次算当前位,就要执行一次while()实现”借1“这样时间代价有点大,写代码也要考虑很多
下面是参考答案的 _cacl()
ps:课后题我是先自己做,再看的答案,所以思路有些不同, 建议大家也先自己做

#小甲鱼的参考答案
	#https://www.jianshu.com/p/c78d54ad1fce
    # 内部方法,计算运行时间
    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
        print(self.prompt)

解法2. 我的思路(加法思想)
     每位的进制不同,让时间的转换看起来复杂,但受到2进制 8进制计算的摧残这么久,我们可以很容易的想到:其实都一样
(加法思想)通过我从组成原理里面了解不多的电路知识,加法器不就是一种很好的方式吗。
一个加数、一个被加数、一个进位标识符
加法器不需要了解当前位的进位,最终会产生多大的影响,只要有进位我就修改标识tag就好(就好比99999+1)
.
     所以我逆向做了一个”减法器“ 。每当当前位需要“借1”时,就修改tag,不管之后”位“怎样计算。
     至于可以实现的理由嘛,其实很简单,例如10进制 单位相加(减)最多9+9=18,也就是说进位最多也就是1。那做一个标志位tag,下一“位”计算时多加(减)一个1就好,至于什么进制,并不重要
.
只是一种理解,真正的计算机只有加法器(再加上一些额外电路就可以实现 + - * / ++ or and 之类的)
下面是代码:

## #内部方法 计算运行时间
    def _calc (self):
        self.lasted = []
        self.prompt = "总共运行了"
        tag = 0
        temp = []
        
        for index in range(6):  #计算
            rindex = 5 - index #低位到高位计算

            #是否借位
            if tag:
                self.lasted.append(self.end[rindex] - self.begin[rindex] - 1)
            else  :
                self.lasted.append(self.end[rindex] - self.begin[rindex])
            tag = 0
            
            #取正数 秒分 时 天 月 (没有年是因为年不可能是负数 自然进不来if语句)
            if self.lasted[index] <0 : 
                if index <= 1:
                    self.lasted[index] = 60 - self.begin[rindex] + self.end[rindex] - 1
                elif index <=2:
                    self.lasted[index] = 24 - self.begin[rindex] + self.end[rindex] - 1
                elif index <=3:
                    self.lasted[index] = 31 - self.begin[rindex] + self.end[rindex] - 1
                else:
                    self.lasted[index] = 12 - self.begin[rindex] + self.end[rindex] - 1

                tag = 1		#只要进来if 那就是产生了借位
        
        for index in range(6):  #正序输出
            rindex = 5 - index
            if self.lasted[rindex] :                     #问题2 当前位不够减可能出现负数 (解决)
                self.prompt += (str(self.lasted[rindex]) + self.unit[index])
        
        #为下一轮计时初始化
        self.begin = 0
        self.end = 0

关于解法2的一点小探讨:
”时间复杂度和空间复杂度方面“:
我的方法是两个if语句的判断,第二的情况多所以判断要复杂些,但是 if 相对于 while 还是要简单些,cpu执行时 空间复杂度while较有优, 时间复杂度if一般情况下较优(现在对于代码的评判一般先考虑时间复杂度,特殊情况下也要考虑内存和硬盘限制)。
这是建立在大量数据前提下,但是现代的编程软件都会有自己的编译方式,而且while循环层不多,这个程序也不复杂,真正跑起来,两种方法也差不多
.
”算法题方面“
我再写完这段程序之后,突然发觉之前做过类似的题目,忘记是HDOJ还是牛客网了(哎没广告费啊-_-)。仔细一想 解法2的方式确实,方便理解.。如果参加竞赛,是愿意写一个能记住且稍微优秀的方案,还是一个费脑子的方案。(这里只是对于代码的探讨,我个人还是很崇拜甲鱼大大的,网上其他的一些视频真心感觉是在读课本啊)
.
“药不能停”
写到这里,其实都可以看出代码在计算天数转月份时会出错,就像小甲鱼说的如果我们不及时纠正,我们会在错误的道路上越走越远……
所以学习用永无止境啊

1.2 作业1

1.2.1 时间的正确输出2

为毛一个月一定是31天?不知道可能也是30天或者29天吗?(上一题我们的答案是假设一个月31天)。没错,如果要正确得到月份的天数,我们还需要考虑是否闰年,还有每月的最大天数,所以太麻烦了……如果我们不及时就在,我们会在错误的道路上越走越远……
.
原始代码出处
链接:https://www.jianshu.com/p/c78d54ad1fce
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
我做了一点小修改

如何解决进制转换问题,这里参考答案给出的方法很暴力,就是直接改成秒,不再计算时天之类的。

但是我感觉”本轮计时共进行了12464862.02秒 “不符合我的阅读习惯(懒得自己算)
所有稍作修改,添加了__StoD()方法改成了天,时,分……

1.2.2 代码2

还是先上代码

import time as myti

class MyTimer:                    #问题3 如何更加精确(已解决)
    def __init__(self):
        self.unit = [ "天", "时", "分", "秒","毫秒"]
        self.prompt = "请先启动计时器"
        self.lasted = []
        self.pre_lasted = 0         
        self.begin = 0
        self.end = 0
        self.default_timer = myti.perf_counter  #perf_counter()返回计时器的精准时间(系统的 运行时间)
                                                #process_time()返回当前进程执行CPU的时间总和
    
    def __str__(self):
        return self.prompt
        
    __repr__ = __str__
    
    #两个计时器时间和
    def __add__(self , other):
        self.prompt = "二者共运行了" 
        result = self.lasted + other.lasted
        tuple2 = self._StoD(result)
        for index in range(5):  #正序输出
            if tuple2[index] :                   
                self.prompt += (str(tuple2[index]) + self.unit[index])
        
        return self.prompt
       

        
    #开始计时
    def start(self):                    #问题1 连续两个start()相当于重新计时(已解决)
        if self.begin:
            print("计时已经开始,请先调用stop()停止当前计时器")
        else:
            self.begin = self.default_timer() 
            self.prompt = "请先调用stop()停止当前计时器"
            print("计时开始……")
    
    #停止计时
    def stop(self):
        if not self.begin:
            print( "请先调用start()启动一个计时器")
        else:
            self.end = self.default_timer()
            print("计时结束")
            self._calc()  
           

    
    
     ## #内部方法 计算运行时间
    def _calc(self):
        self.prompt = "总共运行了"
        self.lasted = self.end - self.begin
        tuple1 = self._StoD(self.lasted)            #问题4 单纯的秒数 阅读不方便(已解决)
        for index in range(5):  #正序输出
            if tuple1[index] :                   
                self.prompt += (str(tuple1[index]) + self.unit[index])
        
        print(self.prompt)
  
        #为下一轮计时初始化
        self.begin = 0
        self.end = 0
    
    # 内部方法 秒改成天
    def _StoD(self,pre_lasted):
        
        str_prelasted = str(pre_lasted)            #转换成字符串
        str_lasted = str_prelasted.partition(".") #切分
        str_lasted_inte = int(str_lasted[0])
        str_lasted_decimal = (str_lasted[2][0:2])

        
        m , s = divmod(str_lasted_inte , 60)    #divmod(div, mod)
        h , m = divmod(m , 60)
        d , h = divmod(h , 24)
        
        return (d,h,m,s,str_lasted_decimal)
        
        
        
    def set_timer(self, timer):
        if timer == "process_time":
            self.default_timer = myti.process_time
        elif timer == "perf_counter":
            self.default_timer = myti.perf_counter
        else :
            print("输入无效")
            
            
            
            
#测试
t1 = MyTimer()
t1.set_timer('perf_counter')
t1.start()
myti.sleep(1.1)
t1.stop()
t2 = MyTimer()
t2.set_timer('perf_counter')
t2.start()
myti.sleep(2.0)
t2.stop()
print(t1 + t2)

1.2.3 秒改成天(日期输出)

问题4 阅读习惯 秒改成天
这里我只是将浮点数转化成字符串,再做一个除法,输出了一个时间元组,也算是对于前面知识的一个回顾

# 内部方法 秒改成天
    def _StoD(self,pre_lasted):
        
        str_prelasted = str(pre_lasted)            #转换成字符串
        str_lasted = str_prelasted.partition(".") #切分
        str_lasted_inte = int(str_lasted[0])		#整数部分
        str_lasted_decimal = (str_lasted[2][0:2])	#小数部分前两位

        
        m , s = divmod(str_lasted_inte , 60)    #divmod(div, mod)
        h , m = divmod(m , 60)
        d , h = divmod(h , 24)
        
        return (d,h,m,s,str_lasted_decimal)
拓展:这样就完美了吗?
没有,在实际运行中,大家可以发现小数点后的位数可能不正确
例如 t1运行1.01s 
     t2运行2.01s
     t1+t2 运行 3.01s
     其原因1 self.lasted是一个浮点数,两个相加有四舍五入
     	  2 python里小数点后进行运算的模块有自己的运算方式,容易出现进位不符合实际的问题(C++浮点运算也有类似问题)
    	(py社区里可能有详细解释,有需要的同学可以去看看)
    	
	怎么解决?
		方法一:self.lasted + other.lasted 相加前就把它们改成两位小数
		方法二:取出小数点后两位改成整型,运算后再拼接到小数点后
					伪代码
					int_decimal = int(str_lasted[2][0:2]) + 另一个
					str_decimal = str(int_decimal)
					str_lasted = str_lasted_inte + '.' +  str_decimal 
					#还有一些需要改的其他部分,这里就不赘述了


参考资料


  1. https://www.bilibili.com/video/BV1Fs411A7HZ?p=45 ↩︎
  2. https://www.jianshu.com/p/c78d54ad1fce ↩︎