Откладывание функций в python

В JavaScript я использую возможность вызова функций, которые будут выполняться позднее, например

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

setTimeout(foo, 1000);

Это не блокирует выполнение другого кода.

Я не знаю, как добиться чего-то подобного в Python. Я могу использовать сон

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

time.sleep(1)
foo()

но это заблокирует выполнение другого кода. (Фактически в моем случае блокирование Python не было бы проблемой само по себе, но я не смог бы unit test метод.)

Я знаю, что потоки предназначены для выполнения вне синхронизации, но мне было интересно, существует ли что-то более простое, похожее на setTimeout или setInterval.

Ответ 1

Вы хотите 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, как этот.

Ответ 2

Чтобы выполнить функцию после задержки или повторить функцию в заданное количество секунд с использованием цикла событий (без потоков), вы могли бы:

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

Примечание: есть небольшая разница в интерфейсе и поведение между этими подходами.

Ответ 3

Асинхронные обратные вызовы, такие как Javascript setTimeout, требуют архитектуры, управляемой событиями.

Асинхронные фреймворки для Python, такие как популярные twisted, имеют CallLater, который делает то, что вы хотите, но это означает принятие управляемой событиями архитектуры в вашем приложении.

Другой вариант - использовать потоки и спать в потоке. Python предлагает timer, чтобы облегчить ожидающую часть. Однако, когда ваш поток просыпается и ваша функция выполняется, он находится в отдельном потоке и должен делать все, что он делает поточно-безопасным способом.

Ответ 4

Извините, я не могу разместить более двух ссылок, поэтому для получения дополнительной информации проверьте PEP 380 и, самое главное, документацию asyncio.

asyncio является предпочтительным решением такого рода вопросов, если вы не настаиваете на потоковой обработке или многопроцессорности. Он разработан и внедрен GvR под названием "Тюльпан". Он был введен GvR на PyCon 2013 с намерением быть одним циклом события, чтобы управлять (и стандартизировать) все циклы событий (например, те, которые были переплетены, gevent и т.д.) и делают их совместимыми друг с другом. асинчио уже упоминалось ранее, но истинная сила асинцио развязана с выходом от.

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

# there only one event loop, let 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 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 not DEAD but can be restarted
loop.run_until_complete(f())
# this closes the loop - now it DEAD
loop.close()

================

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

Ответ 5

JavaScript может сделать это, потому что он запускает вещи в цикле событий. Это можно сделать в Python с помощью цикла событий, такого как Twisted, или с помощью набора инструментов, такого как GLib или Qt.

Ответ 6

Проблема в том, что ваш обычный python script не запускается в рамках. script вызывается и контролирует основной цикл. С JavaScript все скрипты, которые запускаются на вашей странице, работают в рамках, и это среда, которая вызывает ваш метод, когда истекает время ожидания.

Я сам не использовал pyQt (только С++ Qt), но вы можете установить таймер на любом QObject, используя startTimer(). Когда таймер истекает, вызывается обратный вызов вашего метода. Вы также можете использовать QTimer и подключить сигнал тайм-аута к произвольному слоту. Это возможно, потому что Qt запускает цикл событий, который может вызывать ваш метод на более позднем этапе.