web-dev-qa-db-ja.com

Pythonでx秒ごとに関数を繰り返し実行するための最良の方法は何ですか?

私は(Objective Cの NSTimer のように)60秒ごとにPythonで関数を繰り返し実行したいのです。このコードはデーモンとして実行され、事実上cronを使用してpythonスクリプトを呼び出すのに似ていますが、ユーザーがそれを設定する必要はありません。

Pythonに実装されたcronに関するこの質問 では、解決策は事実上x秒間だけ sleep() となるようです。そのような高度な機能は必要ないので、おそらくこのようなものでうまくいくでしょう

while True:
    # Code executed here
    time.sleep(60)

このコードに関して予見できる問題はありますか?

209
DavidM

汎用イベントスケジューラを実装する sched モジュールを使用してください。

import sched, time
s = sched.scheduler(time.time, time.sleep)
def do_something(sc): 
    print "Doing stuff..."
    # do your stuff
    s.enter(60, 1, do_something, (sc,))

s.enter(60, 1, do_something, (s,))
s.run()
186
nosklo

タイムループをシステムクロックにロックするだけです。簡単です。

import time
starttime=time.time()
while True:
  print "tick"
  time.sleep(60.0 - ((time.time() - starttime) % 60.0))
138
Dave Rove

TwistedReactor Pattern を実装したPythonネットワークライブラリです。

from twisted.internet import task, reactor

timeout = 60.0 # Sixty seconds

def doWork():
    #do work here
    pass

l = task.LoopingCall(doWork)
l.start(timeout) # call every sixty seconds

reactor.run()

"while True:sleep(60)"はおそらくうまくいくでしょうが、Twistedはおそらく最終的に必要となる多くの機能(デーモン化、ロギング、例外処理など)を既に実装しており、おそらくより堅牢なソリューションになるでしょう。

62
Aaron Maenpaa

あなたがブロックしている無限ループの代わりにあなたの関数を定期的に実行するブロックしない方法が欲しいなら、私はスレッド化されたタイマーを使うでしょう。このようにして、あなたのコードは走り続けそして他のタスクを実行しそしてあなたの関数がまだn秒毎に呼ばれるのを持つことができる。私はこのテクニックを長い間CPU /ディスク/ネットワークを集中的に使用するタスクの進行状況情報を印刷するためによく使っています。

これは私がstart()とstop()をコントロールした同様の質問で投稿したコードです。

from threading import Timer

class RepeatedTimer(object):
    def __init__(self, interval, function, *args, **kwargs):
        self._timer     = None
        self.interval   = interval
        self.function   = function
        self.args       = args
        self.kwargs     = kwargs
        self.is_running = False
        self.start()

    def _run(self):
        self.is_running = False
        self.start()
        self.function(*self.args, **self.kwargs)

    def start(self):
        if not self.is_running:
            self._timer = Timer(self.interval, self._run)
            self._timer.start()
            self.is_running = True

    def stop(self):
        self._timer.cancel()
        self.is_running = False

使用法:

from time import sleep

def hello(name):
    print "Hello %s!" % name

print "starting..."
rt = RepeatedTimer(1, hello, "World") # it auto-starts, no need of rt.start()
try:
    sleep(5) # your long-running job goes here...
finally:
    rt.stop() # better in a try/finally block to make sure the program ends!

特徴:

  • 標準ライブラリのみ、外部依存はしません
  • start()stop()は、タイマーが既に開始/停止されていても複数回呼び出しても安全です。
  • 呼び出される関数は、位置引数と名前付き引数を持つことができます。
  • intervalはいつでも変更できます。次回の実行後に有効になります。 argskwargs、さらにfunctionも同じです。
46
MestreLion

私が信じるより簡単な方法:

import time

def executeSomething():
    #code here
    time.sleep(60)

while True:
    executeSomething()

このようにしてあなたのコードは実行され、その後60秒間待ってから再び実行され、待つ、実行する、等々。

32
Itxaka

以下は、時間の経過によるドリフィットを回避するMestreLionのコードの更新です。

import threading 
import time

class RepeatedTimer(object):
  def __init__(self, interval, function, *args, **kwargs):
    self._timer = None
    self.interval = interval
    self.function = function
    self.args = args
    self.kwargs = kwargs
    self.is_running = False
    self.next_call = time.time()
    self.start()

  def _run(self):
    self.is_running = False
    self.start()
    self.function(*self.args, **self.kwargs)

  def start(self):
    if not self.is_running:
      self.next_call += self.interval
      self._timer = threading.Timer(self.next_call - time.time(), self._run)
      self._timer.start()
      self.is_running = True

  def stop(self):
    self._timer.cancel()
    self.is_running = False
16
eraoul
import time, traceback

def every(delay, task):
  next_time = time.time() + delay
  while True:
    time.sleep(max(0, next_time - time.time()))
    try:
      task()
    except Exception:
      traceback.print_exc()
      # in production code you might want to have this instead of course:
      # logger.exception("Problem while executing repetitive task.")
    # skip tasks if we are behind schedule:
    next_time += (time.time() - next_time) // delay * delay + delay

def foo():
  print("foo", time.time())

every(5, foo)

残りのコードをブロックせずにこれを実行したい場合は、これを使用して独自のスレッドで実行させることができます。

import threading
threading.Thread(target=lambda: every(5, foo)).start()

このソリューションは、他のソリューションではほとんど見られないいくつかの機能を組み合わせたものです。

  • 例外処理:このレベルで可能な限り例外は適切に処理されます。 e。プログラムを中断することなく、デバッグ目的でログに記録されます。
  • チェーニングなし:あなたが多くの答えで見つけることができる(次のイベントをスケジュールするための)一般的なチェーンのような実装は、何かがスケジューリングメカニズム(threading.Timerまたは何でも)でうまくいかないという点で脆いです。チェーンを終了します。問題の理由がすでに修正されていても、それ以上の実行は行われません。単純なループと単純なsleep()による待機は、比較するとはるかに堅牢です。
  • ドリフトはありません私の解決策はそれが実行されることになっている時間を正確に追跡します。実行時間に依存するドリフトはありません(他の多くの解決策のように)。
  • スキップ: 1回の実行に時間がかかりすぎると、私の解決策はタスクをスキップします(たとえば、5秒ごとにXを実行しますが、Xは6秒かかりました)。これは標準のcronの動作です(そして正当な理由のために)。他の多くの解決策は、遅れることなく単純に連続して数回タスクを実行します。ほとんどの場合(例:クリーンアップタスク)、これは望ましくありません。 の場合は、代わりにnext_time += delayを使用してください。
10
Alfe

私はしばらく前に同様の問題に直面しました。多分 http://cronus.readthedocs.org 助けになるかも?

V0.2では、次のスニペットが動作します

import cronus.beat as beat

beat.set_rate(2) # 2 Hz
while beat.true():
    # do some time consuming work here
    beat.sleep() # total loop duration would be 0.5 sec
5
Anay

Thatとcronの主な違いは例外がデーモンを永久に殺すということです。あなたは例外キャッチャーとロガーでラップしたいかもしれません。

4
bobince

考えられる答えの1つ:

import time
t=time.time()

while True:
    if time.time()-t>10:
        #run your task here
        t=time.time()
1
sks

これがMestreLionからのコードに合わせたバージョンです。元の機能に加えて、このコード:

1)特定の時間にタイマーを起動するために使用されるfirst_intervalを追加します(呼び出し側はfirst_intervalを計算して渡す必要があります)。

2)元のコードで競合状態を解決します。元のコードでは、制御スレッドが実行中のタイマーのキャンセルに失敗した場合(「タイマーを停止し、タイマーのアクションの実行をキャンセルします。これはタイマーがまだ待機段階にある場合にのみ機能します。」から引用 https://docs.python.org/2/library/threading.html )、タイマーは際限なく実行されます。

class RepeatedTimer(object):
def __init__(self, first_interval, interval, func, *args, **kwargs):
    self.timer      = None
    self.first_interval = first_interval
    self.interval   = interval
    self.func   = func
    self.args       = args
    self.kwargs     = kwargs
    self.running = False
    self.is_started = False

def first_start(self):
    try:
        # no race-condition here because only control thread will call this method
        # if already started will not start again
        if not self.is_started:
            self.is_started = True
            self.timer = Timer(self.first_interval, self.run)
            self.running = True
            self.timer.start()
    except Exception as e:
        log_print(syslog.LOG_ERR, "timer first_start failed %s %s"%(e.message, traceback.format_exc()))
        raise

def run(self):
    # if not stopped start again
    if self.running:
        self.timer = Timer(self.interval, self.run)
        self.timer.start()
    self.func(*self.args, **self.kwargs)

def stop(self):
    # cancel current timer in case failed it's still OK
    # if already stopped doesn't matter to stop again
    if self.timer:
        self.timer.cancel()
    self.running = False
1
dproc

例:現在の現地時間を表示する

import datetime
import glib
import logger

def get_local_time():
    current_time = datetime.datetime.now().strftime("%H:%M")
    logger.info("get_local_time(): %s",current_time)
    return str(current_time)

def display_local_time():
    logger.info("Current time is: %s", get_local_time())
    return True

# call every minute
glib.timeout_add(60*1000, display_local_time)
0
rise.riyo
    ''' tracking number of times it prints'''
import threading

global timeInterval
count=0
def printit():
  threading.Timer(timeInterval, printit).start()
  print( "Hello, World!")
  global count
  count=count+1
  print(count)
printit

if __== "__main__":
    timeInterval= int(input('Enter Time in Seconds:'))
    printit()
0
raviGupta

私はTkinter after()メソッドを使用しています。これは(先に示したschedモジュールのように)「ゲームを盗む」ことはしません。つまり、他のものを並行して実行することができます。

import Tkinter

def do_something1():
  global n1
  n1 += 1
  if n1 == 6: # (Optional condition)
    print "* do_something1() is done *"; return
  # Do your stuff here
  # ...
  print "do_something1() "+str(n1)
  tk.after(1000, do_something1)

def do_something2(): 
  global n2
  n2 += 1
  if n2 == 6: # (Optional condition)
    print "* do_something2() is done *"; return
  # Do your stuff here
  # ...
  print "do_something2() "+str(n2)
  tk.after(500, do_something2)

tk = Tkinter.Tk(); 
n1 = 0; n2 = 0
do_something1()
do_something2()
tk.mainloop()

do_something1()do_something2()は、並行して、どんなインターバル速度でも実行できます。ここでは、2番目のものが2倍速く実行されます。どちらかの機能を終了させる条件として単純なカウンターを使用したことにも注意してください。プログラムが終了するまでどのような機能を実行するか(例えば時計)なら、他の好きなものを使うことができます。

0
Apostolos

私はこれを使用して1時間に60のイベントを発生させ、ほとんどのイベントは1分後の同じ秒数で発生します。

import math
import time
import random

TICK = 60 # one minute tick size
TICK_TIMING = 59 # execute on 59th second of the tick
TICK_MINIMUM = 30 # minimum catch up tick size when lagging

def set_timing():

    now = time.time()
    elapsed = now - info['begin']
    minutes = math.floor(elapsed/TICK)
    tick_elapsed = now - info['completion_time']
    if (info['tick']+1) > minutes:
        wait = max(0,(TICK_TIMING-(time.time() % TICK)))
        print ('standard wait: %.2f' % wait)
        time.sleep(wait)
    Elif tick_elapsed < TICK_MINIMUM:
        wait = TICK_MINIMUM-tick_elapsed
        print ('minimum wait: %.2f' % wait)
        time.sleep(wait)
    else:
        print ('skip set_timing(); no wait')
    drift = ((time.time() - info['begin']) - info['tick']*TICK -
        TICK_TIMING + info['begin']%TICK)
    print ('drift: %.6f' % drift)

info['tick'] = 0
info['begin'] = time.time()
info['completion_time'] = info['begin'] - TICK

while 1:

    set_timing()

    print('hello world')

    #random real world event
    time.sleep(random.random()*TICK_MINIMUM)

    info['tick'] += 1
    info['completion_time'] = time.time()

実際の状況に応じて、あなたは長さの目盛りを得るかもしれません:

60,60,62,58,60,60,120,30,30,60,60,60,60,60...etc.

しかし60分後には60ティックになります。そしてそれらのほとんどはあなたが好む分への正しいオフセットで起こるでしょう。

私のシステムでは、補正が必要になるまで、1/20秒未満のドリフトが見られます。

この方法の利点はクロックドリフトの解決です。ティックごとに1つの項目を追加するようなことをしていて、1時間あたり60の項目が追加されると予想している場合、これは問題を引き起こす可能性があります。ドリフトを考慮しないと、移動平均などの2次的な指示によって過去のデータが深すぎると見なされ、出力が不正確になる可能性があります。

0
litepresence