What is the best way to repeatedly execute a function every x seconds in Python?
我想永远每60秒在Python中重复执行一个函数(就像目标C中的NSTimer一样)。 这段代码将作为守护进程运行,实际上就像使用cron每分钟调用python脚本一样,但不需要用户设置。
在这个关于用Python实现的cron的问题中,解决方案似乎实际上只是sleep()x秒。 我不需要这样的高级功能,所以也许这样的东西可行
1 2 3 | while True: # Code executed here time.sleep(60) |
这段代码有可预见的问题吗?
使用sched模块,它实现了一个通用的事件调度程序。
1 2 3 4 5 6 7 8 9 | import sched, time s = sched.scheduler(time.time, time.sleep) def do_something(sc): print"Doing stuff..." # do your stuff s.enter(60, 1, do_something, (sc,)) s.enter(60, 1, do_something, (s,)) s.run() |
只需将时间循环锁定到系统时钟即可。简单。
1 2 3 4 5 | import time starttime=time.time() while True: print"tick" time.sleep(60.0 - ((time.time() - starttime) % 60.0)) |
您可能想要考虑Twisted,它是一个实现Reactor Pattern的Python网络库。
1 2 3 4 5 6 7 8 9 10 11 12 | from twisted.internet import task, reactor timeout = 60.0 # Sixty seconds def doWork(): #do work here pass l = task.LoopingCall(doWork) l.start(timeout) # call every sixty seconds reactor.run() |
虽然"while True:sleep(60)"可能会工作Twisted可能已经实现了你最终需要的许多功能(如bobince所指出的守护进程,日志记录或异常处理),并且可能是一个更强大的解决方案
如果你想要一种非阻塞方式来定期执行你的函数,而不是阻塞无限循环,我会使用一个线程计时器。这样,您的代码可以继续运行并执行其他任务,并且每隔n秒仍然会调用您的函数。我在很长的CPU /磁盘/网络密集型任务中使用这种技术来打印进度信息。
这是我在类似问题中发布的代码,包含start()和stop()控件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | from threading import Timer class RepeatedTimer(object): def __init__(self, interval, function, *args, **kwargs): self._timer = None self.interval = interval self.function = function self.args = args self.kwargs = kwargs self.is_running = False self.start() def _run(self): self.is_running = False self.start() self.function(*self.args, **self.kwargs) def start(self): if not self.is_running: self._timer = Timer(self.interval, self._run) self._timer.start() self.is_running = True def stop(self): self._timer.cancel() self.is_running = False |
用法:
1 2 3 4 5 6 7 8 9 10 11 | from time import sleep def hello(name): print"Hello %s!" % name print"starting..." rt = RepeatedTimer(1, hello,"World") # it auto-starts, no need of rt.start() try: sleep(5) # your long-running job goes here... finally: rt.stop() # better in a try/finally block to make sure the program ends! |
特征:
- 仅限标准库,无外部依赖项
-
即使定时器已经启动/停止,
start() 和stop() 也可以安全地多次调用 - 要调用的函数可以有位置和命名参数
-
您可以随时更改
interval ,它将在下次运行后生效。args ,kwargs 甚至function 也是如此!
我认为更简单的方法是:
1 2 3 4 5 6 7 8 | import time def executeSomething(): #code here time.sleep(60) while True: executeSomething() |
这样你的代码就会被执行,然后等待60秒然后它再次执行,等待,执行等......
无需复杂化:D
以下是MestreLion代码的更新,可以避免随着时间的推移而进行漫游:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import threading import time class RepeatedTimer(object): def __init__(self, interval, function, *args, **kwargs): self._timer = None self.interval = interval self.function = function self.args = args self.kwargs = kwargs self.is_running = False self.next_call = time.time() self.start() def _run(self): self.is_running = False self.start() self.function(*self.args, **self.kwargs) def start(self): if not self.is_running: self.next_call += self.interval self._timer = threading.Timer(self.next_call - time.time(), self._run) self._timer.start() self.is_running = True def stop(self): self._timer.cancel() self.is_running = False |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import time, traceback def every(delay, task): next_time = time.time() + delay while True: time.sleep(max(0, next_time - time.time())) try: task() except Exception: traceback.print_exc() # in production code you might want to have this instead of course: # logger.exception("Problem while executing repetitive task.") # skip tasks if we are behind schedule: next_time += (time.time() - next_time) // delay * delay + delay def foo(): print("foo", time.time()) every(5, foo) |
如果你想在不阻塞剩余代码的情况下执行此操作,可以使用它来让它在自己的线程中运行:
1 2 | import threading threading.Thread(target=lambda: every(5, foo)).start() |
该解决方案结合了其他解决方案中很少结合的几种功能:
- 异常处理:在此级别上尽可能正确处理异常,即:即记录以进行调试,而不会中止我们的程序。
-
没有链接:你在许多答案中找到的常见的类链实现(用于调度下一个事件)在调度机制(
threading.Timer 或其他)中出现任何错误的方面是脆弱的,这将终止链。即使问题的原因已经解决,也不会再发生进一步的执行。与简单的sleep() 相比,一个简单的循环更加稳健。 - 没有漂移:我的解决方案可以准确跟踪它应该运行的时间。根据执行时间没有漂移(如许多其他解决方案中那样)。
-
跳过:如果一次执行耗费太多时间,我的解决方案将跳过任务(例如,每五秒执行一次X,但X需要6秒)。这是标准的cron行为(并且有充分的理由)。然后,许多其他解决方案只是连续几次执行任务而没有任何延迟。对于大多数情况(例如清理任务),这是不希望的。如果需要,只需使用
next_time += delay 。
一段时间后我遇到了类似的问题。可能是http://cronus.readthedocs.org可能有帮助吗?
对于v0.2,以下代码段有效
1 2 3 4 5 6 | import cronus.beat as beat beat.set_rate(2) # 2 Hz while beat.true(): # do some time consuming work here beat.sleep() # total loop duration would be 0.5 sec |
它与cron之间的主要区别在于异常将终止该守护进程。您可能希望使用异常捕获器和记录器进行换行。
这是MestreLion代码的改编版本。
除了原始函数,这段代码:
1)添加first_interval用于在特定时间触发定时器(调用者需要计算first_interval并传入)
2)用原始代码解决竞争条件。在原始代码中,如果控制线程未能取消正在运行的计时器("停止计时器,并取消执行计时器的操作。这只有在计时器仍处于等待阶段时才有效。"引自https:// docs.python.org/2/library/threading.html),计时器将无休止地运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class RepeatedTimer(object): def __init__(self, first_interval, interval, func, *args, **kwargs): self.timer = None self.first_interval = first_interval self.interval = interval self.func = func self.args = args self.kwargs = kwargs self.running = False self.is_started = False def first_start(self): try: # no race-condition here because only control thread will call this method # if already started will not start again if not self.is_started: self.is_started = True self.timer = Timer(self.first_interval, self.run) self.running = True self.timer.start() except Exception as e: log_print(syslog.LOG_ERR,"timer first_start failed %s %s"%(e.message, traceback.format_exc())) raise def run(self): # if not stopped start again if self.running: self.timer = Timer(self.interval, self.run) self.timer.start() self.func(*self.args, **self.kwargs) def stop(self): # cancel current timer in case failed it's still OK # if already stopped doesn't matter to stop again if self.timer: self.timer.cancel() self.running = False |
一个可能的答案:
1 2 3 4 5 6 7 | import time t=time.time() while True: if time.time()-t>10: #run your task here t=time.time() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ''' tracking number of times it prints''' import threading global timeInterval count=0 def printit(): threading.Timer(timeInterval, printit).start() print("Hello, World!") global count count=count+1 print(count) printit if __name__ =="__main__": timeInterval= int(input('Enter Time in Seconds:')) printit() |
我使用Tkinter after()方法,它不会"窃取游戏"(就像之前介绍的sched模块一样),即它允许其他东西并行运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import Tkinter def do_something1(): global n1 n1 += 1 if n1 == 6: # (Optional condition) print"* do_something1() is done *"; return # Do your stuff here # ... print"do_something1()"+str(n1) tk.after(1000, do_something1) def do_something2(): global n2 n2 += 1 if n2 == 6: # (Optional condition) print"* do_something2() is done *"; return # Do your stuff here # ... print"do_something2()"+str(n2) tk.after(500, do_something2) tk = Tkinter.Tk(); n1 = 0; n2 = 0 do_something1() do_something2() tk.mainloop() |
例如,显示当前本地时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import datetime import glib import logger def get_local_time(): current_time = datetime.datetime.now().strftime("%H:%M") logger.info("get_local_time(): %s",current_time) return str(current_time) def display_local_time(): logger.info("Current time is: %s", get_local_time()) return True # call every minute glib.timeout_add(60*1000, display_local_time) |
我使用它来导致每小时60个事件,大多数事件发生在整个分钟后的相同秒数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | import math import time import random TICK = 60 # one minute tick size TICK_TIMING = 59 # execute on 59th second of the tick TICK_MINIMUM = 30 # minimum catch up tick size when lagging def set_timing(): now = time.time() elapsed = now - info['begin'] minutes = math.floor(elapsed/TICK) tick_elapsed = now - info['completion_time'] if (info['tick']+1) > minutes: wait = max(0,(TICK_TIMING-(time.time() % TICK))) print ('standard wait: %.2f' % wait) time.sleep(wait) elif tick_elapsed < TICK_MINIMUM: wait = TICK_MINIMUM-tick_elapsed print ('minimum wait: %.2f' % wait) time.sleep(wait) else: print ('skip set_timing(); no wait') drift = ((time.time() - info['begin']) - info['tick']*TICK - TICK_TIMING + info['begin']%TICK) print ('drift: %.6f' % drift) info['tick'] = 0 info['begin'] = time.time() info['completion_time'] = info['begin'] - TICK while 1: set_timing() print('hello world') #random real world event time.sleep(random.random()*TICK_MINIMUM) info['tick'] += 1 info['completion_time'] = time.time() |
根据实际情况,您可能会得到长度的标记:
1 | 60,60,62,58,60,60,120,30,30,60,60,60,60,60...etc. |
但在60分钟结束时,你将有60个蜱虫;并且它们中的大多数将以正确的偏移量发生到您喜欢的那一分钟。
在我的系统上,我得到典型的漂移<1/20秒,直到需要进行校正。
这种方法的优点是时钟漂移的分辨率;如果您正在执行诸如每个刻度附加一个项目并且您希望每小时附加60个项目,这可能会导致问题。如果不考虑漂移,可能会导致移动平均等次要指示将数据过深地考虑在过去,从而导致输出错误。