Python中setInterval的等效实现

21 投票
4 回答
12955 浏览
提问于 2025-04-16 12:54

我最近在网上发了一个问题,关于如何在Python中延迟执行一个函数(类似于Javascript中的setTimeout)。结果发现,使用threading.Timer其实很简单(当然,前提是这个函数不和其他代码共享状态,否则在事件驱动的环境中会出现问题)。

现在我想做得更好,模仿一下setInterval。如果你不熟悉Javascript,setInterval可以让你每隔x秒重复调用一个函数,而不会阻塞其他代码的执行。我创建了一个示例装饰器:

import time, threading

def setInterval(interval, times = -1):
    # This will be the actual decorator,
    # with fixed interval and times parameter
    def outer_wrap(function):
        # This will be the function to be
        # called
        def wrap(*args, **kwargs):
            # This is another function to be executed
            # in a different thread to simulate setInterval
            def inner_wrap():
                i = 0
                while i != times:
                    time.sleep(interval)
                    function(*args, **kwargs)
                    i += 1
            threading.Timer(0, inner_wrap).start()
        return wrap
    return outer_wrap

可以这样使用:

@setInterval(1, 3)
def foo(a):
    print(a)

foo('bar')
# Will print 'bar' 3 times with 1 second delays

看起来运行得不错。不过我有几个问题:

  • 感觉这个实现有点复杂,我担心可能错过了更简单或更好的方法。
  • 这个装饰器可以在没有第二个参数的情况下被调用,这样的话它会一直运行下去。说到“一直”,我指的是永远——即使从主线程调用sys.exit()也无法停止它,按Ctrl+c也没用。唯一能停止它的方法是从外部杀掉Python进程。我希望能从主线程发送一个信号来停止这个回调。但我对线程还很陌生——我该如何在它们之间进行通信呢?

编辑 如果有人好奇,这是在jd的帮助下得到的装饰器的最终版本。

import threading

def setInterval(interval, times = -1):
    # This will be the actual decorator,
    # with fixed interval and times parameter
    def outer_wrap(function):
        # This will be the function to be
        # called
        def wrap(*args, **kwargs):
            stop = threading.Event()

            # This is another function to be executed
            # in a different thread to simulate setInterval
            def inner_wrap():
                i = 0
                while i != times and not stop.isSet():
                    stop.wait(interval)
                    function(*args, **kwargs)
                    i += 1

            t = threading.Timer(0, inner_wrap)
            t.daemon = True
            t.start()
            return stop
        return wrap
    return outer_wrap

它可以像上面那样使用,设置固定的重复次数。

@setInterval(1, 3)
def foo(a):
    print(a)

foo('bar')
# Will print 'bar' 3 times with 1 second delays

或者可以一直运行,直到收到停止信号。

import time

@setInterval(1)
def foo(a):
    print(a)

stopper = foo('bar')

time.sleep(5)
stopper.set()
# It will stop here, after printing 'bar' 5 times.

4 个回答

2

也许用递归调用定时器会更简单:

from threading import Timer
import atexit

class Repeat(object):

    count = 0
    @staticmethod
    def repeat(rep, delay, func):
        "repeat func rep times with a delay given in seconds"

        if Repeat.count < rep:
            # call func, you might want to add args here
            func()
            Repeat.count += 1
            # setup a timer which calls repeat recursively
            # again, if you need args for func, you have to add them here
            timer = Timer(delay, Repeat.repeat, (rep, delay, func))
            # register timer.cancel to stop the timer when you exit the interpreter
            atexit.register(timer.cancel)
            timer.start()

def foo():
    print "bar"

Repeat.repeat(3,2,foo)

atexit 这个功能可以让你在按下 CTRL-C 时发出停止的信号。

3

也许这些是Python中最简单的setInterval等效实现:

 import threading

 def set_interval(func, sec):
    def func_wrapper():
        set_interval(func, sec) 
        func()  
    t = threading.Timer(sec, func_wrapper)
    t.start()
    return t
11

你的解决方案看起来不错。

与线程进行沟通有好几种方法。如果你想让一个线程停止,可以使用 threading.Event(),它有一个 wait() 方法,你可以用它来替代 time.sleep()

stop_event = threading.Event()
...
stop_event.wait(1.)
if stop_event.isSet():
    return
...

为了让你的线程在程序结束时能够退出,记得在调用 start() 之前,把它的 daemon 属性设置为 True。这同样适用于 Timer() 对象,因为它们是 threading.Thread 的子类。详细信息可以查看 这里

撰写回答