Откладывание функций в Python
В JavaScript я привык вызывать функции, которые будут выполняться позже, как это
function foo() {
alert('bar');
}
setTimeout(foo, 1000);
Это не блокирует выполнение другого кода.
Я не знаю, как добиться чего-то подобного в Python. Я могу использовать сон
import time
def foo():
print('bar')
time.sleep(1)
foo()
но это заблокирует выполнение другого кода. (На самом деле в моем случае блокирование Python само по себе не было бы проблемой, но я не смог бы провести модульное тестирование метода.)
Я знаю, что потоки предназначены для несинхронного выполнения, но мне было интересно, что-то проще, похожее на setTimeout
или же setInterval
существует.
6 ответов
Вы хотите 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
на основе подхода, как этот.
Чтобы выполнить функцию после задержки или повторить функцию через заданное количество секунд, используя цикл обработки событий (без потоков), вы можете:
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 представляет новый временный API для асинхронного ввода-вывода - asyncio
модуль:
#!/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
Примечание: между этими подходами есть небольшое различие в интерфейсе и поведении.
Асинхронные обратные вызовы, такие как Javascript setTimeout
требует управляемой событиями архитектуры.
Асинхронные фреймворки для Python, такие как популярный витой, имеют CallLater
который делает то, что вы хотите, но это означает принятие в вашем приложении архитектуры, управляемой событиями.
Другая альтернатива - использовать потоки и спать в потоке. Python предоставляет таймер, чтобы упростить ожидание. Однако, когда ваш поток просыпается и ваша функция выполняется, он находится в отдельном потоке и должен делать все, что он делает, потокобезопасным способом.
Извините, я не могу опубликовать более двух ссылок, поэтому для получения дополнительной информации, пожалуйста, проверьте PEP 380 и, самое главное, документацию asyncio.
Asyncio является предпочтительным решением для такого рода вопросов, если вы не настаиваете на многопоточности или многопроцессорности. Он разработан и реализован GvR под названием "Тюльпан". Он был представлен GvR на PyCon 2013 с целью быть одним циклом событий, чтобы управлять (и стандартизировать) всеми циклами событий (такими как витые, 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
>>>
JavaScript может сделать это, потому что он запускает события в цикле событий. Это может быть сделано в Python посредством использования цикла событий, такого как Twisted, или с помощью инструментария, такого как GLib или Qt.
Проблема в том, что ваш обычный скрипт на python не работает в фреймворке. Сценарий вызывается и контролирует основной цикл. В JavaScript все сценарии, которые выполняются на вашей странице, выполняются в среде, и именно эта среда вызывает ваш метод по истечении времени ожидания.
Я сам не использовал pyQt (только C++ Qt), но вы можете установить таймер для любого QObject, используя startTimer(). Когда таймер истекает, обратный вызов вашего метода вызывается. Вы также можете использовать QTimer и подключить сигнал тайм-аута к произвольному слоту. Это возможно, потому что Qt запускает цикл обработки событий, который может вызвать ваш метод на более позднем этапе.