Отложенные функции в python

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

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

setTimeout(foo, 1000);

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

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

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

time.sleep(1)
foo()

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

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

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

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

выход

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

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


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

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

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


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

asyncio является предпочтительным решением такого рода вопросов, если вы настаиваю на продевать нитку или multiprocessing. Он разработан и реализован компанией GvR под названием "Тюльпан". Он был введен GvR на команда PyCon с намерением быть одним циклом событий, чтобы управлять (и стандартизировать) всеми циклами событий (например, в twisted, 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 запускает цикл событий, который может вызвать ваш метод на более позднем этапе.