Выполнение периодических действий в Python
Я работаю над Windows. Я хочу выполнить функцию foo() каждые 10 секунд.
Как это сделать?
Ответы
Ответ 1
В конце foo()
создайте Timer
, который вызывает foo()
сам через 10 секунд.
Потому что Timer
создайте новый thread
для вызова foo()
.
Вы можете делать другие вещи без блокировки.
import time, threading
def foo():
print(time.ctime())
threading.Timer(10, foo).start()
foo()
#output:
#Thu Dec 22 14:46:08 2011
#Thu Dec 22 14:46:18 2011
#Thu Dec 22 14:46:28 2011
#Thu Dec 22 14:46:38 2011
Ответ 2
Просто спать в течение 10 секунд или с помощью threading.Timer(10,foo)
приведет к дрейфу времени начала. (Возможно, вам это неинтересно, или это может быть серьезным источником проблем в зависимости от вашей конкретной ситуации.) Для этого могут быть две причины - неточности в времени пробуждения вашего потока или времени выполнения для вашей функции.
Вы можете увидеть некоторые результаты в конце этого сообщения, но сначала пример того, как его исправить. Вам нужно отслеживать, когда должна быть вызвана ваша функция, а не когда она действительно вызвана и учитывает разницу.
Здесь версия, которая слегка смещается:
import datetime, threading
def foo():
print datetime.datetime.now()
threading.Timer(1, foo).start()
foo()
Его вывод выглядит следующим образом:
2013-08-12 13:05:36.483580
2013-08-12 13:05:37.484931
2013-08-12 13:05:38.485505
2013-08-12 13:05:39.486945
2013-08-12 13:05:40.488386
2013-08-12 13:05:41.489819
2013-08-12 13:05:42.491202
2013-08-12 13:05:43.492486
2013-08-12 13:05:44.493865
2013-08-12 13:05:45.494987
2013-08-12 13:05:46.496479
2013-08-12 13:05:47.497824
2013-08-12 13:05:48.499286
2013-08-12 13:05:49.500232
Вы можете видеть, что подсчет подсерий постоянно увеличивается, и, таким образом, время начала "дрейфует".
Это код, который правильно учитывает дрейф:
import datetime, threading, time
next_call = time.time()
def foo():
global next_call
print datetime.datetime.now()
next_call = next_call+1
threading.Timer( next_call - time.time(), foo ).start()
foo()
Его вывод выглядит следующим образом:
2013-08-12 13:21:45.292565
2013-08-12 13:21:47.293000
2013-08-12 13:21:48.293939
2013-08-12 13:21:49.293327
2013-08-12 13:21:50.293883
2013-08-12 13:21:51.293070
2013-08-12 13:21:52.293393
Здесь вы можете видеть, что больше не было увеличения в суб-второй раз.
Если ваши события происходят очень часто, вы можете запустить таймер в одном потоке, а не запускать новый поток для каждого события. Хотя учет дрейфа будет выглядеть следующим образом:
import datetime, threading, time
def foo():
next_call = time.time()
while True:
print datetime.datetime.now()
next_call = next_call+1;
time.sleep(next_call - time.time())
timerThread = threading.Thread(target=foo)
timerThread.start()
Однако ваше приложение не будет нормально работать, вам нужно будет уничтожить поток таймера. Если вы хотите нормально выйти, когда ваше приложение будет завершено, без ручного уничтожения потока, вы должны использовать
timerThread = threading.Thread(target=foo)
timerThread.daemon = True
timerThread.start()
Ответ 3
Удивлен, чтобы не найти решение с использованием генератора для синхронизации. Я просто разработал это для своих целей.
Это решение: однопоточное, без создания экземпляра объекта в каждом периоде, использует генератор для времени, твердое твердое время с точностью до точности модуля time
(в отличие от нескольких решений, которые я пытался использовать для обмена столами).
Примечание: для Python 2.x, замените next(g)
ниже на g.next()
.
import time
def do_every(period,f,*args):
def g_tick():
t = time.time()
count = 0
while True:
count += 1
yield max(t + count*period - time.time(),0)
g = g_tick()
while True:
time.sleep(next(g))
f(*args)
def hello(s):
print('hello {} ({:.4f})'.format(s,time.time()))
time.sleep(.3)
do_every(1,hello,'foo')
Результаты, например:
hello foo (1421705487.5811)
hello foo (1421705488.5811)
hello foo (1421705489.5809)
hello foo (1421705490.5830)
hello foo (1421705491.5803)
hello foo (1421705492.5808)
hello foo (1421705493.5811)
hello foo (1421705494.5811)
hello foo (1421705495.5810)
hello foo (1421705496.5811)
hello foo (1421705497.5810)
hello foo (1421705498.5810)
hello foo (1421705499.5809)
hello foo (1421705500.5811)
hello foo (1421705501.5811)
hello foo (1421705502.5811)
hello foo (1421705503.5810)
Обратите внимание, что этот пример включает в себя симуляцию процессора, выполняющего что-то еще за .3 секунды за каждый период. Если вы изменили его, чтобы быть случайным каждый раз, это не имело бы значения. Максимальное значение в строке yield
служит для защиты sleep
от отрицательных чисел в случае, если вызываемая функция занимает больше времени, чем указанный период. В этом случае он будет выполняться немедленно и составить потерянное время в момент следующего выполнения.
Ответ 4
Возможно, модуль расписания будет отвечать вашим потребностям.
В качестве альтернативы рассмотрите использование объекта Timer.
Ответ 5
Здесь хорошая реализация с использованием класса Thread: http://g-off.net/software/a-python-repeatable-threadingtimer-class
приведенный ниже код немного более быстрый и грязный:
from threading import Timer
from time import sleep
def hello():
print "hello, world"
t = Timer(3,hello)
t.start()
t = Timer(3, hello)
t.start() # after 3 seconds, "hello, world" will be printed
# timer will wake up ever 3 seconds, while we do something else
while True:
print "do something else"
sleep(10)
Ответ 6
Вот простая однопоточная версия спящего режима, которая дрейфует, но пытается автокорректировать, когда обнаруживает дрейф.
ПРИМЕЧАНИЕ. Это будет работать, только если выполнены следующие 3 разумных предположения:
- Период времени намного больше, чем время выполнения выполняемой функции.
- Выполняемая функция занимает примерно одинаковое количество времени для каждого вызова
- Количество дрейфа между вызовами меньше секунды
-
from datetime import timedelta
from datetime import datetime
def exec_every_n_seconds(n,f):
first_called=datetime.now()
f()
num_calls=1
drift=timedelta()
time_period=timedelta(seconds=n)
while 1:
time.sleep(n-drift.microseconds/1000000.0)
current_time = datetime.now()
f()
num_calls += 1
difference = current_time - first_called
drift = difference - time_period* num_calls
print "drift=",drift
Ответ 7
Это приведет к вставке 10 секундного спящего режима между каждым вызовом до foo()
, что примерно соответствует тому, что вы просили, когда вызов завершается быстро.
import time
while True:
foo()
time.sleep(10)
изменить: Выполнять другие действия, пока ваш foo()
вызывается в фоновом потоке
import time
import sys
import threading
def foo():
sys.stdout.write('({}) foo\n'.format(time.ctime()))
def foo_target():
while True:
foo()
time.sleep(10)
t = threading.Thread(target=foo_target)
t.daemon = True
t.start()
raw_input('do other things...')
Ответ 8
Вы можете выполнить свою задачу в другом потоке. threading.Timer
позволит вам выполнить заданный обратный вызов один раз по прошествии некоторого времени, если вы хотите выполнить свою задачу, например, до тех пор, пока обратный вызов возвращает True
(на самом деле это означает glib.timeout_add
, но вы можете не устанавливать его в окнах) или пока вы не отмените его, вы можете использовать этот код:
import logging, threading, functools
import time
logging.basicConfig(level=logging.NOTSET,
format='%(threadName)s %(message)s')
class PeriodicTimer(object):
def __init__(self, interval, callback):
self.interval = interval
@functools.wraps(callback)
def wrapper(*args, **kwargs):
result = callback(*args, **kwargs)
if result:
self.thread = threading.Timer(self.interval,
self.callback)
self.thread.start()
self.callback = wrapper
def start(self):
self.thread = threading.Timer(self.interval, self.callback)
self.thread.start()
def cancel(self):
self.thread.cancel()
def foo():
logging.info('Doing some work...')
return True
timer = PeriodicTimer(1, foo)
timer.start()
for i in range(2):
time.sleep(2)
logging.info('Doing some other work...')
timer.cancel()
Пример вывода:
Thread-1 Doing some work...
Thread-2 Doing some work...
MainThread Doing some other work...
Thread-3 Doing some work...
Thread-4 Doing some work...
MainThread Doing some other work...
Примечание. Обратный вызов не выполняется при каждом выполнении интервала. Интервал - это время, в течение которого поток ожидает завершения обратного вызова в последний раз и в следующий раз.
Ответ 9
Если вы хотите запустить foo() внутри python script каждые 10 секунд, вы можете что-то сделать в этих строках.
import time
def foo():
print "Howdy"
while True:
foo()
time.sleep(10)