开发者

Postponing functions in python

In JavaScript I am used to being able to call functions to be execute开发者_如何学Pythond at a later time, like this

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

setTimeout(foo, 1000);

This does not block the execution of other code.

I do not know how to achieve something similar in Python. I can use sleep

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

time.sleep(1)
foo()

but this will block the execution of other code. (Actually in my case blocking Python would not be a problem in itself, but I would not be able to unit test the method.)

I know threads are designed for out-of-sync execution, but I was wondering whether something easier, similar to setTimeout or setInterval exists.


To execute a function after a delay or to repeat a function in given number of seconds using an event-loop (no threads), you could:

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")

Output

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")

Output

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

Twisted

#!/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")

Output

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

Asyncio

Python 3.4 introduces new provisional API for asynchronous IO -- 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")

Output

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

Note: there is a slight difference in the interface and behavior between these approaches.


You want a Timer object from the threading module.

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)

If you want to repeat, here's a simple solution: instead of using Timer, just use Thread but pass it a function that works somewhat like this:

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

This won't do infinite loops because that could result in a thread that won't die and other unpleasant behavior if not done right. A more sophisticated approach might use an Event-based approach, like this one.


Asynchronous callbacks like Javascript's setTimeout require an event-driven architecture.

Asynchronous frameworks for Python like the popular twisted have CallLater which does what you want, but it means adopting the event-driven architecture in your application.

Another alternative is to use threads and to sleep in a thread. Python providers a timer to make the waiting part easy. However, when your thread awakes and your function executes, it is in a separate thread and must do whatever it does in a thread-safe manner.


Sorry, I can't post more than 2 links, so for more information please check PEP 380 and most importantly the documentation of asyncio.

asyncio is the preferred solution to this kind of question unless you insist on threading or multiprocessing. It is designed and implemented by GvR under the name "Tulip". It has been introduced by GvR on PyCon 2013 with the intention to be the one event-loop to rule (and standardize) all event-loops (like the ones in twisted, gevent, etc.) and make them compatible with each other. asyncio has been mentioned before, but the true power of asyncio is unleashed with yield from.

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


JavaScript can do this because it runs things in an event loop. This can be done in Python through use of an event loop such as Twisted, or via a toolkit such as GLib or Qt.


The problem is that your normal python script doesn't run in a framework. The script gets called and is in control of the main loop. With JavaScript, all the scripts that runs on your page runs in a framework and it is the framework that invokes your method when the timeout elapses.

I haven't used pyQt myself (only C++ Qt), but you can set a timer on any QObject using startTimer(). When the timer elapse, a callback on your method is invoked. You can also use QTimer and connect the timeout signal to an arbitrary slot. This is possible because Qt runs an event loop that can call your method at a later stage.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜