Откладывание функций в Python - PullRequest
10 голосов
/ 03 марта 2011

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

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

setTimeout(foo, 1000);

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

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

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

time.sleep(1)
foo()

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

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

Ответы [ 6 ]

14 голосов
/ 26 декабря 2012

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

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

выход

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

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

8 голосов
/ 03 марта 2011

Требуется объект 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 подход, такой, как этот .

4 голосов
/ 03 марта 2011

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

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

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

2 голосов
/ 28 апреля 2015

Извините, я не могу опубликовать более 2 ссылок, поэтому для получения дополнительной информации, пожалуйста, проверьте 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
>>>
1 голос
/ 03 марта 2011

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

0 голосов
/ 03 марта 2011

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

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

...