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.
精彩评论