Python中setInterval的等效实现
我最近在网上发了一个问题,关于如何在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
的子类。详细信息可以查看 这里