python重复执行_关于计时器:在Python中每x秒重复执行一次函数的最佳方法是什么?...

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

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

1

2

3while 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模块,它实现了一个通用的事件调度程序。

1

2

3

4

5

6

7

8

9import 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之后开始

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

1

2

3

4

5import 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网络库。

1

2

3

4

5

6

7

8

9

10

11

12from 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()控件:

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

26from 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

11from 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

我认为更简单的方法是:

1

2

3

4

5

6

7

8import 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代码的更新,可以避免随着时间的推移而进行漫游:

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

29import 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

19import 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,以下代码段有效

1

2

3

4

5

6import 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

37class 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

7import 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

27import 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解决方案中使用两个或多个具有不同间隔的预定函数,它将与您的工作完全相同。

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

例如,显示当前本地时间

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15import 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

43import 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个项目,这可能会导致问题。如果不考虑漂移,可能会导致移动平均等次要指示将数据过深地考虑在过去,从而导致输出错误。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值