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

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

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

setTimeout(foo, 1000);

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

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

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

time.sleep(1)
foo()

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

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


person Andrea    schedule 03.03.2011    source источник


Ответы (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, подобный этому.

person senderle    schedule 03.03.2011
comment
Позвольте мне попросить вариант. Что, если я хочу повторять вызов каждые x секунд, как в setInterval? Я мог бы позволить функции запускать свой собственный таймер в конце, но будет ли это порождать бесконечно много потоков? - person Andrea; 03.03.2011
comment
@ Андреа, да, это не лучший способ сделать это. Извините за задержку; пора было спать. См. выше для первого подхода. - person senderle; 03.03.2011
comment
Спасибо. На самом деле вскоре после того, как я спросил, я начал делать декоратор для этого поведения. Единственная проблема заключалась в том, как остановить его работу навсегда, что я решил благодаря вкладу в этот вопрос. stackoverflow.com/questions/5179467/ я наконец-то обновил вопрос рабочей версией декоратора :-) - person Andrea; 03.03.2011
comment
@ Андреа, ах, да, я собирался предложить использовать Event для завершения цикла. Хороший декоратор. - person senderle; 03.03.2011
comment
подход, основанный на событиях, не должен быть сложным, например, call_repeatedly(interval, func, *args) - person jfs; 30.10.2017

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

Ткинтер

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

ГТК

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

Асинкио

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

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

person jfs    schedule 26.12.2012
comment
Я искал версию LoopingCall от Twisted для aysncio. Спасибо за все примеры! - person Dan Gayle; 25.06.2014

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

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

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

person Will    schedule 03.03.2011

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

asyncio является предпочтительным решением для такого рода вопросов, если только вы не настаиваете на многопоточности или многопроцессорности. Он разработан и реализован компанией GvR под названием «Тюльпан». Он был представлен GvR на PyCon 2013 с намерением стать единственным событием. -loop, чтобы управлять (и стандартизировать) все циклы событий (например, в Twisted, gevent и т. д.) и сделать их совместимыми друг с другом. asyncio уже упоминалось ранее, но истинная мощь asyncio раскрывается с помощью 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
>>>
person Amo    schedule 28.04.2015
comment
Не могли бы вы добавить более подробную информацию о вашем ответе? - person abarisone; 28.04.2015
comment
Конечно, что-то особенное вы хотите, чтобы я добавил? Я прокомментирую строки, которые, возможно, проясняют, как это работает. - person Amo; 29.04.2015

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

person Ignacio Vazquez-Abrams    schedule 03.03.2011

Проблема в том, что ваш обычный скрипт Python не работает в фреймворке. Сценарий вызывается и контролирует основной цикл. С помощью JavaScript все сценарии, которые выполняются на вашей странице, выполняются в фреймворке, и именно фреймворк вызывает ваш метод по истечении времени ожидания.

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

person Dirk    schedule 03.03.2011
comment
Спасибо за ваш ответ, но переключение моего кода на приложение Qt только для этого метода не вариант. - person Andrea; 03.03.2011