zoukankan      html  css  js  c++  java
  • 第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:Users1AppDataLocalProgramsPythonPython38libidlelib
    pc.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
    修改了add魔法方法

    结果:

    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

    本博客所有文章仅用于学习、研究和交流目的,欢迎非商业性质转载。

    博主的文章主要是记录一些学习笔记、作业等。文章来源也已表明,由于博主的水平不高,不足和错误之处在所难免,希望大家能够批评指出。

    博主是利用读书、参考、引用、抄袭、复制和粘贴等多种方式打造成自己的文章,请原谅博主成为一个无耻的文档搬运工!

  • 相关阅读:
    fwt
    fft,ntt
    loj6077
    高维前缀和
    hihocoder 1496 寻找最大值
    HDU 5977 Garden of Eden
    扩展crt
    有标号的DAG计数I~IV
    BZOJ 3160 万径人踪灭
    Codeforces Round #524 (Div. 2) F
  • 原文地址:https://www.cnblogs.com/hale547/p/13382182.html
Copyright © 2011-2022 走看看