在Python 3中安排重复事件

66 投票
14 回答
132059 浏览
提问于 2025-04-15 20:08

我想在Python 3中安排一个每分钟重复运行的事件。

我见过一个叫 sched.scheduler 的类,但我在想有没有其他的方法可以做到这一点。我听说可以用多个线程来实现这个,我对此也不介意。

我的主要任务是请求一些JSON数据,然后解析这些数据;这些数据的值会随着时间变化。

如果要使用 sched.scheduler,我需要创建一个循环来请求它安排事件在一个小时内运行:

scheduler = sched.scheduler(time.time, time.sleep)

# Schedule the event. THIS IS UGLY!
for i in range(60):
    scheduler.enter(3600 * i, 1, query_rate_limit, ())

scheduler.run()

还有什么其他的方法可以做到这一点呢?

14 个回答

25

我对这个话题的简单看法:

from threading import Timer

class RepeatedTimer(object):
    def __init__(self, interval, function, *args, **kwargs):
        self._timer     = None
        self.function   = function
        self.interval   = interval
        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!

特点:

  • 只使用标准库,不需要其他外部依赖
  • 采用了Alex Martnelli建议的模式
  • start()stop()可以安全地多次调用,即使计时器已经开始或停止
  • 要调用的函数可以接受位置参数和命名参数
  • 你可以随时更改interval,下次运行时就会生效。argskwargs甚至function也是一样!
41

你可以使用schedule这个库。它可以在Python 2.7和3.3上运行,而且非常轻量级:

import schedule
import time

def job():
   print("I'm working...")

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)

while 1:
   schedule.run_pending()
   time.sleep(1)
69

你可以使用 threading.Timer,但这也只是安排一个一次性的事件,就像调度器对象的 .enter 方法一样。

在任何编程语言中,把一次性调度器变成定期调度器的常见做法是让每个事件在指定的时间间隔内重新安排自己。例如,使用 sched 时,我不会像你那样用循环,而是会用类似下面的方式:

def periodic(scheduler, interval, action, actionargs=()):
    scheduler.enter(interval, 1, periodic,
                    (scheduler, interval, action, actionargs))
    action(*actionargs)

然后通过调用来启动整个“永远定期调度”的过程

periodic(scheduler, 3600, query_rate_limit)

或者,我可以用 threading.Timer 来代替 scheduler.enter,但它们的模式其实很相似。

如果你需要更细致的变化(比如在特定时间停止定期调度或在某些条件下停止),这也不难,只需要加几个额外的参数就可以了。

撰写回答