私は(Objective Cの NSTimer のように)60秒ごとにPythonで関数を繰り返し実行したいのです。このコードはデーモンとして実行され、事実上cronを使用してpythonスクリプトを呼び出すのに似ていますが、ユーザーがそれを設定する必要はありません。
Pythonに実装されたcronに関するこの質問 では、解決策は事実上x秒間だけ sleep() となるようです。そのような高度な機能は必要ないので、おそらくこのようなものでうまくいくでしょう
while True:
# Code executed here
time.sleep(60)
このコードに関して予見できる問題はありますか?
汎用イベントスケジューラを実装する 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()
タイムループをシステムクロックにロックするだけです。簡単です。
import time
starttime=time.time()
while True:
print "tick"
time.sleep(60.0 - ((time.time() - starttime) % 60.0))
Twisted は Reactor 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はおそらく最終的に必要となる多くの機能(デーモン化、ロギング、例外処理など)を既に実装しており、おそらくより堅牢なソリューションになるでしょう。
あなたがブロックしている無限ループの代わりにあなたの関数を定期的に実行するブロックしない方法が欲しいなら、私はスレッド化されたタイマーを使うでしょう。このようにして、あなたのコードは走り続けそして他のタスクを実行しそしてあなたの関数がまだ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
はいつでも変更できます。次回の実行後に有効になります。 args
、kwargs
、さらにfunction
も同じです。私が信じるより簡単な方法:
import time
def executeSomething():
#code here
time.sleep(60)
while True:
executeSomething()
このようにしてあなたのコードは実行され、その後60秒間待ってから再び実行され、待つ、実行する、等々。
以下は、時間の経過によるドリフィットを回避する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
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()
このソリューションは、他のソリューションではほとんど見られないいくつかの機能を組み合わせたものです。
threading.Timer
または何でも)でうまくいかないという点で脆いです。チェーンを終了します。問題の理由がすでに修正されていても、それ以上の実行は行われません。単純なループと単純なsleep()
による待機は、比較するとはるかに堅牢です。next_time += delay
を使用してください。私はしばらく前に同様の問題に直面しました。多分 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
Thatとcronの主な違いは例外がデーモンを永久に殺すということです。あなたは例外キャッチャーとロガーでラップしたいかもしれません。
考えられる答えの1つ:
import time
t=time.time()
while True:
if time.time()-t>10:
#run your task here
t=time.time()
これが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
例:現在の現地時間を表示する
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)
''' 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()
私は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倍速く実行されます。どちらかの機能を終了させる条件として単純なカウンターを使用したことにも注意してください。プログラムが終了するまでどのような機能を実行するか(例えば時計)なら、他の好きなものを使うことができます。
私はこれを使用して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次的な指示によって過去のデータが深すぎると見なされ、出力が不正確になる可能性があります。