推迟python中的函数

时间:2011-03-03 06:29:13

标签: python multithreading setinterval

在JavaScript中,我习惯于能够调用稍后要执行的函数,比如

function foo() {
    alert('bar');
}

setTimeout(foo, 1000);

这不会阻止其他代码的执行。

我不知道如何在Python中实现类似的功能。我可以使用睡眠

import time
def foo():
    print('bar')

time.sleep(1)
foo()

但这会阻止其他代码的执行。 (实际上在我的情况下,阻止Python本身并不是问题,但我无法对该方法进行单元测试。)

我知道线程是为不同步执行而设计的,但我想知道是否存在更容易的事情,类似于setTimeoutsetInterval

6 个答案:

答案 0 :(得分:14)

要在延迟后执行函数或使用事件循环(无线程)在给定的秒数内重复某个函数,您可以:

Tkinter的

#!/usr/bin/env python
from Tkinter import Tk

def foo():
    print("timer went off!")

def countdown(n, bps, root):
    if n == 0:
        root.destroy() # exit mainloop
    else:
        print(n)
        root.after(1000 / bps, countdown, n - 1, bps, root)  # repeat the call

root = Tk()
root.withdraw() # don't show the GUI window
root.after(4000, foo) # call foo() in 4 seconds
root.after(0, countdown, 10, 2, root)  # show that we are alive
root.mainloop()
print("done")

输出

10
9
8
7
6
5
4
3
timer went off!
2
1
done

基于GTK

#!/usr/bin/env python
from gi.repository import GObject, Gtk

def foo():
    print("timer went off!")

def countdown(n): # note: a closure could have been used here instead
    if n[0] == 0:
        Gtk.main_quit() # exit mainloop
    else:
        print(n[0])
        n[0] -= 1
        return True # repeat the call

GObject.timeout_add(4000, foo) # call foo() in 4 seconds
GObject.timeout_add(500, countdown, [10])
Gtk.main()
print("done")

输出

10
9
8
7
6
5
4
timer went off!
3
2
1
done

#!/usr/bin/env python
from twisted.internet import reactor
from twisted.internet.task import LoopingCall

def foo():
    print("timer went off!")

def countdown(n):
    if n[0] == 0:
        reactor.stop() # exit mainloop
    else:
        print(n[0])
        n[0] -= 1

reactor.callLater(4, foo) # call foo() in 4 seconds
LoopingCall(countdown, [10]).start(.5)  # repeat the call in .5 seconds
reactor.run()
print("done")

输出

10
9
8
7
6
5
4
3
timer went off!
2
1
done

ASYNCIO

Python 3.4为异步IO引入了新的provisional API - asyncio module

#!/usr/bin/env python3.4
import asyncio

def foo():
    print("timer went off!")

def countdown(n):
    if n[0] == 0:
        loop.stop() # end loop.run_forever()
    else:
        print(n[0])
        n[0] -= 1

def frange(start=0, stop=None, step=1):
    while stop is None or start < stop:
        yield start
        start += step #NOTE: loss of precision over time

def call_every(loop, seconds, func, *args, now=True):
    def repeat(now=True, times=frange(loop.time() + seconds, None, seconds)):
        if now:
            func(*args)
        loop.call_at(next(times), repeat)
    repeat(now=now)

loop = asyncio.get_event_loop()
loop.call_later(4, foo) # call foo() in 4 seconds
call_every(loop, 0.5, countdown, [10]) # repeat the call every .5 seconds
loop.run_forever()
loop.close()
print("done")

输出

10
9
8
7
6
5
4
3
timer went off!
2
1
done

注意:这些方法之间的界面和行为略有不同。

答案 1 :(得分:8)

您需要Timer模块中的threading对象。

from threading import Timer
from time import sleep

def foo():
    print "timer went off!"
t = Timer(4, foo)
t.start()
for i in range(11):
    print i
    sleep(.5)

如果你想重复一遍,这里有一个简单的解决方案:不要使用Timer,而是使用Thread,但是传递一个有点像这样的函数:

def call_delay(delay, repetitions, func, *args, **kwargs):             
    for i in range(repetitions):    
        sleep(delay)
        func(*args, *kwargs)

这不会做无限循环,因为这可能导致一个不会死的线程和其他不愉快的行为,如果做得不对。更复杂的方法可能使用基于Event的方法like this one

答案 2 :(得分:4)

Javascript的setTimeout之类的异步回调需要一个事件驱动的架构。

Python的异步框架(如流行的twisted具有CallLater可以满足您的需求,但这意味着在您的应用程序中采用事件驱动的体系结构。

另一种选择是使用线程并在线程中休眠。 Python提供程序timer使等待部分变得容易。但是,当你的线程唤醒并且你的函数执行时,它在一个单独的线程中,并且必须以线程安全的方式做它做的任何事情。

答案 3 :(得分:2)

抱歉,我发不到2个以上的链接,所以如需了解更多信息,请查看 PEP 380 ,最重要的是 asyncio

asyncio是此类问题的首选解决方案,除非您坚持进行线程或多处理。它由GvR以“Tulip”的名义设计和实施。它已由GvR在PyCon 2013上引入,旨在成为一个事件循环来规则(和标准化)所有事件循环(如twisted,gevent等中的那些)并使它们与每个事件循环兼容其他。之前已经提到过asyncio,但asyncio的真正威力来自来自的收益。

# asyncio is in standard lib for latest python releases (since 3.3)
import asyncio

# there's only one event loop, let's fetch that
loop = asyncio.get_event_loop()

# this is a simple reminder that we're dealing with a coro
@asyncio.coroutine
def f():
    for x in range(10):
        print(x)
        # we return with a coroutine-object from the function, 
        # saving the state of the execution to return to this point later
        # in this case it's a special sleep
        yield from asyncio.sleep(3)

# one of a few ways to insert one-off function calls into the event loop
loop.call_later(10, print, "ding!")
# we insert the above function to run until the coro-object from f is exhausted and 
# raises a StopIteration (which happens when the function would return normally)
# this also stops the loop and cleans up - keep in mind, it's not DEAD but can be restarted
loop.run_until_complete(f())
# this closes the loop - now it's DEAD
loop.close()

=====

>>> 
0
1
2
3
ding!
4
5
6
7
8
9
>>>

答案 4 :(得分:1)

JavaScript可以做到这一点,因为它在事件循环中运行。这可以通过使用事件循环(如Twisted)或通过工具包(如GLib或Qt)在Python中完成。

答案 5 :(得分:0)

问题是你的普通python脚本不能在框架中运行。脚本被调用并控制主循环。使用JavaScript,页面上运行的所有脚本都在框架中运行,并且它是在超时过去时调用方法的框架。

我自己没有使用过pyQt(只有C ++ Qt),但你可以使用startTimer()在任何QObject上设置一个计时器。当计时器过去时,将调用方法的回调。您还可以使用QTimer并将超时信号连接到任意插槽。这是可能的,因为Qt运行一个事件循环,可以在稍后阶段调用您的方法。