我想永远每60秒在Python中重复执行一个函数(就像目标C中的NSTimer一样)。 这段代码将作为守护进程运行,实际上就像使用cron每分钟调用python脚本一样,但不需要用户设置。

在这个关于用Python实现的cron的问题中,解决方案似乎实际上只是sleep()x秒。 我不需要这样的高级功能,所以也许这样的东西可行

while True:
# Code executed here
time.sleep(60)

这段代码有可预见的问题吗?

一个迂腐的观点,但可能很关键,代码上面的代码不会每60秒执行一次,它会在执行之间产生60秒的差距。如果执行的代码完全没有时间,则每60秒才会发生一次。

Dupe:stackoverflow.com/questions/373335/…

time.sleep(60)也可以提前和退回

我仍然想知道:这段代码有任何可预见的问题吗?

"可预见的问题"是你不能期望通过使用time.sleep(60)每小时进行60次迭代。因此,如果您每次迭代附加一个项目并保留设置长度列表......该列表的平均值将不代表一致的"时间段";因此,诸如"移动平均线"之类的功能可以引用太旧的数据点,这会扭曲您的指示。

有关简单的示例,请参阅Python调度。

@Banana是的,你可以期待任何问题,因为你的脚本没有每60秒执行一次。例如。我开始做这样的事情来分割视频流和上传,我最终得到了5-10秒的延迟,因为媒体队列正在缓冲,而我在循环内处理数据。这取决于您的数据。如果该功能是某种简单的监视器,它会警告你,例如,当你的磁盘已满时,你应该没有任何问题。如果你正在检查核电站警告警报,你可能最终得到一个城市完全炸毁了x

使用sched模块,它实现了一个通用的事件调度程序。

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()

sched模块用于调度函数在一段时间后运行,如何使用它来每隔x秒重复一次函数调用而不使用time.sleep()?

@Baishampayan:安排新的跑步。

基于sched的Kronos提供了更高级别的界面:razorvine.net/download/kronos.py由TurboGears使用。

那么在packages.python.org/APScheduler上的apscheduler也应该在这一点上提到。

shed模块的文档也指向threading.Timer类,它更适合多线程环境。 docs.python.org/2/library/threading.html#threading.Timer可以在sched模块文档中找到一个示例。

注意:此版本可能会漂移。您可以使用enterabs()来避免它。这是一个非漂移版本进行比较。

你好,没有输出,代码不起作用。

@ J.F.Sebastian:为什么这个版本可以漂移?

@JavaSa:因为"你的东西"不是瞬间的,time.sleep的错误可能会在这里积累。"每隔X秒执行一次"和"反复执行延迟~X秒"是不一样的。另见此评论

谢谢,接受的答案应该改变,并非所有人都阅读评论......

这不回答这个问题。

好的,但如何退出此计时器(循环)?

@Apostolos它运行直到没有更多的预定事件,所以你可以不安排另一次运行,s.enter(60, 1, do_something, (sc,))是调度新运行的行,只是当你想要停止循环时不运行该部分

如果你删除这一行,那么它不再是一个运行的间隔:它只是执行的延迟;它运行一次,就是这样。我的问题是,你需要添加一个这个循环将终止的条件。例如。 if ...: return这将终止循环。顺便说一下,还有其他更重要的东西:这个循环,正如在示例中使用的那样,锁定程序,即在终止之前没有其他任何东西可以运行。

@Apostolos如果,在do_something()函数内,你执行if ...: return而不调用将终止循环的s.enter(...),因为没有其他任何东西将被安排运行。代码流将从s.run()调用解锁,并在此后有代码时继续。

确切地说,这就是我的意思。如果需要终止循环,则需要if ...: return条件。但最重要的是,正如我所说,这种方法"锁定"程序的流程,即在s.run()之后你不能做任何事情(直到循环终止)。 (请参阅我提供的解决方案,进一步说明。)

@Apostolos您提供的解决方案使用Tkinter.Tk.mainloop()来执行相同操作。用你的术语:在mainloop()之后你不能做任何事情(直到循环结束)。唯一的区别是你正在使用一个UI库,而我正在使用专门用于安排不尝试创建UI窗口的调用的库。

我想我已经解释过了,但我会根据你的最后评论再试一次。两者之间的区别在于,当你的时钟工作时,即在发出s.run()命令之后,没有其他任何东西可以运行,而在我的方法中,时钟开始工作,你仍然可以在那之后做你想做的任何动作。只有在完成了想要做的所有事情后,才能给出mainloop()命令。所以差异真的很大。

@Apostolos呵呵,但是,如果时钟在到达mainloop()之前到期,则不会调用函数... after()调用仅在mainloop运行时才有效,而不是之前。您可以计算通过的时间,并在计划第一次运行时减去该时间(如果需要)

你喜欢就好。

python如何处理堆栈?由于递归,这不会由于尚未完成的函数而执行几乎无限的堆栈吗?我需要每天安排几个数百万的功能。

@DGoiko预定的功能被添加到列表中,不会被重复调用。当前函数完成后,调度程序将查找要调用的下一个函数。所以调用堆栈永远不会堆积 - 你不会遇到递归问题。

@nosklo因此,如果我做对了,s.enter(60,1,do_something,(sc,))将任务放在一个列表中,在60秒后执行一次,然后,如果调度程序设置为运行,计数器开始向下计时,直到零,函数执行的时刻。如果向列表中添加新元素,则会在执行.enter后立即开始计时。我对吗?

看来你好吗?对于@DGoiko来说,如果你计划一个新的跑步,时钟就会在enter之后开始

只需将时间循环锁定到系统时钟即可。简单。

import time
starttime=time.time()
while True:
print"tick"
time.sleep(60.0 - ((time.time() - starttime) % 60.0))

+1。你的和twisted答案是每隔x秒运行一个函数的唯一答案。其余的在每次调用后执行该函数延迟x秒。

如果你在哪里添加一些代码,这需要超过一秒......它会抛出时间并开始落后......在这种情况下接受的答案是正确的...任何人都可以循环一个简单的打印命令让它每秒都运行一次......

由于存在的影响,我更喜欢from time import time, sleep;)

@Mayhem:错了。 1-如果代码花费的时间超过句点,则没有解决方案可行(否则最终会耗尽资源)。 2-这个解决方案可以跳过一个勾号,但它总是在整个时期边界(在这种情况下是一分钟)运行。这里不能只是"循环一个简单的打印命令" - 代码的目的是避免多次迭代后的漂移。

工作非常好。如果你开始将它同步到某个时间,则无需减去starttime:time.sleep(60 - time.time() % 60)对我来说一直很好。我已经将它用作time.sleep(1200 - time.time() % 1200)并且它给了我:00 :20 :40的日志,正如我想要的那样。

@ J.F.Sebastian:最后% 60的目的是什么?

@AntonSchigur避免多次迭代后的漂移。单个迭代可能会迟早或稍后开始,具体取决于sleep(),timer()精度以及执行循环体所需的时间,但平均迭代总是发生在间隔边界上(即使跳过一些):while keep_doing_it(): sleep(interval - timer() % interval) 。将其与while keep_doing_it(): sleep(interval)进行比较,其中错误可能在多次迭代后累积。

@TrainHeartnet当遗漏模数时,time.time() - starttime的结果将大于设定的时间(在这种情况下为60),因此60 - (time.time() - starttime)的结果将为负,这会导致睡眠功能冻结(不完全是但它只是等待了大量的时间)。在这种情况下,%60可以防止它变得大于60。

这个解决方案在这个线程中具有最少的漂移,但缺点是time.time() - starttime将在一段时间后成为一个非常大的数字。我更喜欢做的是在循环中移动starttime声明。这不太精确,但在使用较小时间时仅具有明显的效果。 -Edit:没关系,它永远不会变得超过timer.timer(),所以只有你的脚本运行了几十亿年才会出现问题

我一直在寻找比我一直使用的time.time() - start_time方法更好的方法,这个方法看起来精确到0.1秒,这对我来说已经足够了。

我认为starttime=time.time()也应该作为while循环内的第一行。

@backslashN不,我相信这是重点(记住初始开始并使用模数) - 防止漂移。我在while循环中有类似的代码(sleep(interval - (end-start))和startTime,但我认为这个解决方案更好。我改编了它,谢谢。

@smoothware,你甚至可以从"time.time() - starttime"删除"starttime = time.time()"和" - starttime"。间隔仍然相等而不是漂移。只是没有连接到循环之前的任何时间点。

您可能想要考虑Twisted,它是一个实现Reactor Pattern的Python网络库。

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所指出的守护进程,日志记录或异常处理),并且可能是一个更强大的解决方案

我知道Twisted可以做到这一点。感谢分享示例代码!

很好的答案,非常准确,没有漂移。我想知道这是否会让CPU在等待执行任务时休眠(等等。)

这种漂移在毫秒级

如果你想要一种非阻塞方式来定期执行你的函数,而不是阻塞无限循环,我会使用一个线程计时器。这样,您的代码可以继续运行并执行其他任务,并且每隔n秒仍然会调用您的函数。我在很长的CPU /磁盘/网络密集型任务中使用这种技术来打印进度信息。

这是我在类似问题中发布的代码,包含start()和stop()控件:

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

用法:

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也是如此!

这个解决方案似乎随着时间而变化;我需要一个版本,旨在每隔n秒调用该函数而不会漂移。我将在一个单独的问题中发布更新。

在def _run(self)中,我试图解决为什么在self.function()之前调用self.start()。你能详细说说吗?我认为通过调用start()首先self.is_running总是False所以我们总是会启动一个新线程。

我想我已经到底了。 @ MestreLion的解决方案每x秒运行一个函数(即t = 0,t = 1x,t = 2x,t = 3x,...),其中在原始海报中,样本代码运行一个函数,其间有x秒间隔。此外,我相信这个解决方案有一个错误,如果interval比function执行的时间短。在这种情况下,self._timer将在start函数中被覆盖。

是的,@ RichieEpiscopo,.start()之后对.function()的调用是在t = 0运行函数。如果function花费的时间超过interval,我认为这不会是一个问题,但是在代码中可能存在一些竞争条件。

这是我能得到的唯一无阻塞方式。谢谢。

@eraoul:是的,这个解决方案确实漂移,虽然它需要几百甚至几千次运行才能漂移一秒钟,具体取决于你的系统。如果这种漂移与您相关,我强烈建议您使用适当的系统调度程序,例如cron

我认为更简单的方法是:

import time
def executeSomething():
#code here
time.sleep(60)
while True:
executeSomething()

这样你的代码就会被执行,然后等待60秒然后它再次执行,等待,执行等......

无需复杂化:D

关键字True应为大写

实际上这是对这个问题最合适的答案!

实际上这不是答案:time sleep()只能在每次执行后等待X秒。例如,如果您的函数需要0.5秒执行并且您使用time.sleep(1),则表示您的函数每1.5秒执行一次,而不是1.您应该使用其他模块和/或线程来确保某些内容适用于Y次在每X秒。

@kommradHomer:Dave Rove的回答表明你可以每隔X秒使用time.sleep()运行一些东西

在我看来,代码应该在while True循环中调用time.sleep(),如:def executeSomething(): print('10 sec left') ; while True: executeSomething(); time.sleep(10)

非常好...使用python 3 ...

以下是MestreLion代码的更新,可以避免随着时间的推移而进行漫游:

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
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
2import threading
threading.Thread(target=lambda: every(5, foo)).start()

该解决方案结合了其他解决方案中很少结合的几种功能:

异常处理:在此级别上尽可能正确处理异常,即:即记录以进行调试,而不会中止我们的程序。

没有链接:你在许多答案中找到的常见的类链实现(用于调度下一个事件)在调度机制(threading.Timer或其他)中出现任何错误的方面是脆弱的,这将终止链。即使问题的原因已经解决,也不会再发生进一步的执行。与简单的sleep()相比,一个简单的循环更加稳健。

没有漂移:我的解决方案可以准确跟踪它应该运行的时间。根据执行时间没有漂移(如许多其他解决方案中那样)。

跳过:如果一次执行耗费太多时间,我的解决方案将跳过任务(例如,每五秒执行一次X,但X需要6秒)。这是标准的cron行为(并且有充分的理由)。然后,许多其他解决方案只是连续几次执行任务而没有任何延迟。对于大多数情况(例如清理任务),这是不希望的。如果需要,只需使用next_time += delay。

不漂流的最佳答案。

upvoted!如何在没有睡眠的情况下这样做,我有一个redis订阅者,有实时数据传入,因此无法承受睡眠但需要每分钟运行一些东西

@PirateApp我会在另一个线程中执行此操作。你可以在同一个线程中完成它,但最后你编写自己的调度系统,这对于评论来说太复杂了。

感谢分享我唯一关心的是我需要访问一个变量来阅读它,读取2个线程中的变量是一个坏主意没有,因此问题

在Python中,由于GIL,在两个线程中访问变量是非常安全的。仅仅读取两个线程永远不应该是一个问题(也不是在其他线程环境中)。仅在没有GIL的系统中从两个不同的线程写入(例如在Java,C ++等中)需要一些显式同步。

一段时间后我遇到了类似的问题。可能是http://cronus.readthedocs.org可能有帮助吗?

对于v0.2,以下代码段有效

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),计时器将无休止地运行。

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

一个可能的答案:

import time
t=time.time()
while True:
if time.time()-t>10:
#run your task here
t=time.time()

这是忙着等待因此非常糟糕。

寻找非阻塞计时器的人的好解决方案。


''' 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模块一样),即它允许其他东西并行运行:

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()

do_something1()和do_something2()可以并行运行,也可以以任何间隔速度运行。在这里,第二个将执行两倍快。还注意我使用一个简单的计数器作为终止任一功能的条件。您可以使用您喜欢的任何其他部分,或者如果您在程序终止之前运行的功能(例如时钟),则可以使用任何其他部分。

小心你的措辞:after不允许并行运行。 Tkinter是单线程的,一次只能做一件事。如果after安排的某些内容正在运行,则它不会与其余代码并行运行。如果do_something1和do_something2都安排在同一时间运行,它们将按顺序运行,而不是并行运行。

@Apostolos你所有的解决方案都是使用tkinter mainloop而不是sched mainloop,所以它的工作方式完全相同,但允许tkinter接口继续响应。如果你没有将tkinter用于其他事情,那么就sched解决方案而言,它不会改变任何东西。您可以在sched解决方案中使用两个或多个具有不同间隔的预定函数,它将与您的工作完全相同。

不,它的工作方式不同。我解释了这个。一个"锁定"程序(即停止流程,你不能做任何其他事情 - 甚至没有按照你的建议开始另一个场景工作)直到它完成而另一个让你的手/自由自由(即你可以做在它开始之后的其他事情。你不必等待它完成。这是一个巨大的差异。如果你尝试过我提出的方法,你会亲眼看到。我试过你的。为什么不是你试试我的?

例如,显示当前本地时间

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个事件,大多数事件发生在整个分钟后的相同秒数:

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()
根据实际情况,您可能会得到长度的标记:
160,60,62,58,60,60,120,30,30,60,60,60,60,60...etc.

但在60分钟结束时,你将有60个蜱虫;并且它们中的大多数将以正确的偏移量发生到您喜欢的那一分钟。

在我的系统上,我得到典型的漂移<1/20秒,直到需要进行校正。

这种方法的优点是时钟漂移的分辨率;如果您正在执行诸如每个刻度附加一个项目并且您希望每小时附加60个项目,这可能会导致问题。如果不考虑漂移,可能会导致移动平均等次要指示将数据过深地考虑在过去,从而导致输出错误。