曲曲的秘密学术基地

纯化欲望、坚持严肃性

欢迎!我是曲泽慧(@zququ),目前在深圳(ICBI,BCBDI,SIAT)任职助理研究员。


病毒学、免疫学及结构生物学背景,可以在 RG 上找到我已发表的论文

本站自2019年7月已访问web counter

python 魔法方法 (4) 简单定制

整理自小甲鱼鱼C论坛

简单定制

下面来做一个案例,要求如下:

  1. 定制一个计时器的类
  2. start和stop方法代表启动定时和停止计时
  3. 假设计时器对象t1,print(t1)和直接调用t1均显示结果
  4. 当计时器未启动或已经停止计时,调用stop方法会给予温馨的提示
  5. 像个计时器对象可以相加:t1+t2
  6. 只能使用提供的有限资源完成

需要用到下面的资源:

  1. 使用time模块的localtime方法获取时间
  2. time.localtime返回struct_time的时间格式
  3. __str__()和__repr__()魔法方法
  4. 其中,__str__()和__repr__()魔法方法的用法如下:
# __str__()的代码示例

>>> class A:
        def __str__(self):
            return "A"
>>> a = A()
>>> print(a)
A
>>> a
>>> <__main__.A object at 0x03260F30>

# __repr__()的代码示例

>>> class B:
        def __repr__(self):
            return "B"

>>> b = B()
>>> b
B

首先谈一下—__str__()的方法,在打印一个实例化对象时,打印的其实和是一个对象的地址。而通过__str__()函数就可以帮助我们打印对象中具体的属性值,因为在python中调用print函数打印实例化对象时就会调用__str__()的方法,如果__str__()中有返回值,就会打印其中的返回值。

而__repr__()本身功能为显示自身的属性,在实例化以后,调用自身就会打印出属性。

import time as t

class Mytimer:
    def __init__(self):
        self.unit = ['年', '月', '天', '小时', '分钟', '秒']
        self.prompt = "还未开始计时"
        self.last_list = []
        self.begin = 0    # 这里如果使用self.start = 0,
                          # 会因为在__init__()方法中使用
                          # 与类中方法相同的名的属性,而覆盖方法
        self.end = 0

    def start(self):
        self.begin = t.localtime()
        self.prompt = "请调用stop()开始计时"
        print("计时开始")

    def stop(self):
        self.end = t.localtime()
        self.calc()
        print("计时结束")

    def calc(self):
        self.last_list = []
        self.prompt = "总共运行了"
        for i in range(6):
            self.list.append(self.stop[i] - self.start[i])
            self.prompt += str(self.last_list[i])

    def __str__(self):
        return self.prompt

    __repr__ = __str__    # 实现print(实例对象)和
                          # 直接调用实例对象都会
                          # 显示结果

    __add__(self, other):
        prompt = '总共运行了'
        result = []
        for i in range(6):
            result.append(self.result[i] + other.result[i])
            if result[i]:
                prompt += (str(result[i]) + self.unit[i])
        return prompt

但是以上的统计运行时间方法只能计算短时间以内的时间,当我们想要计算比较长时间的间隔就会出现问题,比如如果开始计时的时间是2022年2月22日16:30:30,停止时间是2025年1月23日15:30:30就会出现负数(小时:分钟:秒可以通过程序解决,但是月份就会出现闰年问题,变得很难处理)。

对于这一问题,可以用time模块的perf_counter()和procee_time()来计算,其中,

perf_counter()返回计时器的精准时间(系统的运行时间)
process_time()返回当前进程执行CPU的时间总和

尝试通过perf_counter()以及process_time()这两个函数来改写程序,并加入能够连续统计多次运行程序的时间总长:

import time as t

class Mytimer:
    def __init__(self, func, number = 1000000):
        self.prompt = "还未开始计时"
        self.default_timer = t.perf_counter
        self.begin = 0    # 这里如果使用self.start = 0,
                          # 会因为在__init__()方法中使用
                          # 与类中方法相同的名的属性,而覆盖方法

        self.end = 0
        self.lasted = 0.0

    def __str__(self):
        return self.prompt

    __repr__ = __str__    # 实现print(实例对象)和
                          # 直接调用实例对象都会
                          # 显示结果

    def __add__(self, other):
        result = self.lasted + other.lasted
        prompt = '总共运行了%0.2f秒' % result
        return prompt

    def set_timer(self.timer):
        if timer == "process_time":
            self.default_timer = process_time
        elif timer:
            self.default_timer = perf_counter
        else:
            print("输入无效,请重新输入perf_counter或process_time")

    def timing(self):
        self.start = self.defalt_timer()

        for i in range(number):
            self.func()
        self.end = self.default_timer()
        self.lasted = self.end - self.start
        self.prompt = "总共运行了%0.2f秒" % self.lasted

这一讲主要告诉了一个道理,千万不要自己编辑程序计时器。计时器出现问题,失败代价成本太高!

Last One

汇编语言 10.4 10.5 10.6 转移的目的地址在指令,寄存器以及内存中的call指令

10.4 转移的目的地址在指令中的call指令前面讲的call指令,其对应的及其指令中并没有转移的目的地址,而是相对于当前IP的转移位移。“call far ptr 标号”实现的是段间转移。CPU执行此种格式的call指令时,进行如下的操作。 (sp)=(sp)-2 ((ss)*16+(sp))=(CS)(sp)=(sp)-2((ss)*16+(sp))=(IP) (CS)=标号所在段的段地址(IP)=标号在段中的偏移地址 从上面的描述中可以看出,如果我们用汇编语...…

汇编语言More
Next One

汇编语言 10.1 10.2 10.3 ret和retf call指令 依据位移进行转移的call指令

call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计。10.1 ret和retfret指令用栈中的数据,修改IP的内容,从而实现近转移;retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。 CPU执行ret指令时,进行下面两步操作: (IP)=((ss)*16+(sp)) (sp)=(sp)+2栈模拟图如下, 1 IP 2   ...…

汇编语言More