Выполнение периодических действий в 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 от отрицательных чисел в случае, если вызываемая функция занимает больше времени, чем указанный период. В этом случае он будет выполняться немедленно и составить потерянное время в момент следующего выполнения.

Ответ 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)