本文转载自微信公众号《Python作业辅导员》,作者田园浪子。转载本文请联系Python作业导师公众号。所谓定时器,是指在特定的时间间隔执行特定任务的机制。几乎所有的编程语言都有定时器的实现。比如Java有util.Timer和util.TimerTask,JavaScript有setInterval和setTimeout,可以实现非常复杂的定时任务处理。然而,万能的Python竟然没有像样的定时器,真是让人无法理解。刚入门的同学肯定会说:不是有time.sleep吗?定好闹钟睡觉,闹钟一响就起床工作。这不是定时器吗?是的,time.sleep具备定时器的基本要素,但是如果作为定时器使用,有两个致??命的缺陷:一是阻塞主线程,休眠期间什么也做不了;子线程首先由主线程创建。说到这里,熟悉threading模块threading的同学可能会说:threading.Timer是以线程方式运行的,它既不会阻塞主线程,也不需要主线程干预定时任务的执行。这不是一个完美的计时器吗??让我们来看看threading.Timer是如何工作的。下面这段代码演示了threading.Timer的基本用法:do_something函数在启动定时器2秒后以线程方式调用,主线程在等待定时器的2秒内和执行期间仍然可以做其他工作do_something的运行——这里是读取键盘输入,从而阻塞主线程,从而观察定时器的工作情况。importtimeimportthreadingdefdo_something(name,gender='male'):print(time.time(),'时间到了,执行特定任务')print('name:%s,gender:%s'%(name,gender))timer=threading.Timer(2,do_something,args=('Alice',),kwargs={'gender':'female'})timer.start()print(time.time(),'计时开始时间')input('PresstheEnterkeytoend\n')#这里阻塞了主进程。正如我们所料,函数do_something在定时器启动2秒后被调用。在此期间,您可以随时按回车键结束程序。运行这段代码的结果如下。1627438957.4297626计时开始时间按回车键结束1627438959.4299397计时时间到,执行一个具体的任务名称:Alice,gender:female从使用效果来看,threading.Timer是一个简单易用的定时器。但是threading.Timer有明显的缺点,就是不支持连续的定时任务,比如每2秒调用一次do_something函数。如果一定要使用threading.Timer来实现连续计时,那么只能使用类似嵌套的workaround,在do_something函数中再次启动定时器。importtimeimportthreadingdefdo_something(name,gender='male'):globaltimertimer=threading.Timer(2,do_something,args=(name,),kwargs={'gender':gender})timer.start()print(time.time(),'时间到了,执行特定任务')print('name:%s,gender:%s'%(name,gender))time.sleep(5)print(time.time(),'完成特定任务task')timer=threading.Timer(2,do_something,args=('Alice',),kwargs={'gender':'female'})timer.start()input('PressEntertoend\n')#这里阻塞主进程的代码重新定义了do_something函数,在函数开头开始下一个定时任务。之所以放在最前面,是为了保证两次计时的时间间隔尽可能准确。如果是这样,下面的运行结果表明,两次计时的时间间隔比设计的2秒长了大约10毫秒,并且误差不断累积。如果重复执行100次,错误将超过1秒。回车结束1627440628.683803时间到了,执行具体任务姓名:Alice,性别:女1627440633.6890671完成具体任务1627440634.722474预定时间到,执行具体任务Timer的表现不尽如人意,但是这种嵌套的写法,完全颠覆了代码的审美。对于像我这样的代码整洁度程序员来说,是无法容忍和无法接受的。在我看来,一个完美的定时器应该满足以下5个条件,并具有如下图所示的结构。不阻塞主线程同时支持单次定时和连续定时。在线程或进程中执行定时任务。定时任务的线程或进程的创建和运行不影响定时精度。计时精度足够准确,不会累积误差,因为Python没有提供。一个像样的计时器,然后自己写一个。下面的定时器满足了上面提到的5个条件,最短的时间间隔可以低至10毫秒,并且错误不会累积。虽然不完美,但在结构和精度上还是过得去的。importtimeimportthreadingclassPyTimer:"""定时器类"""def__init__(self,func,*args,**kwargs):"""Constructor"""self.func=funcself.args=argsself.kwargs=kwargsself.running=Falsedef_run_func(self):"""运行定时事件函数"""th=threading.Thread(target=self.func,args=self.args,kwargs=self.kwargs)th.setDaemon(True)th.start()def_start(self,interval,once):"""启动定时器的线程函数"""ifinterval<0.010:interval=0.010ifinterval<0.050:dt=interval/10else:dt=0.005ifonce:deadline=time.time()+intervalwhiletime.time()
