Форум сайта python.su
Была у меня задумка, использовать таймер, не зависящий от GUI. В библиотеках, идущих с Питоном, не нашёл в чистом виде, поэтому сделал такой велосипед:
import threading
class Timer:
def __init__(self, function, period = 1.0):
self.func = function
self.period = period
self.timer = None
def start(self):
if self.timer == None:
self.timer = threading.Timer(self.period, function=self.on_timeout)
self.timer.start()
def on_timeout(self):
self.func()
self.timer.run()
def stop(self):
self.timer.cancel()
self.timer = None
if __name__ == '__main__':
def func():
print "Hi!"
timer = Timer(func, 0.5)
timer.start()
raw_input("press Enter for end first step...\n")
timer.stop()
raw_input("press Enter for start second step...\n")
timer.start()
raw_input("press Enter if you want to stop...\n")
timer.stop()
Офлайн
Таймер в GUI библиотеках вроде не на дополнительном потоке основан, а на асинхронности, поэтому при использовании вашего таймера в GUI приложениях возможны глюки.
Офлайн
можно пользовать модуль gobject - там есть такое
Офлайн
У GUI библиотек как правило есть свои таймеры. Так же, по крайней мере это касается GTK/GLib, существует особенность использования потоков (еще по форуму про gobject, gtk и потоки).
..bw
Офлайн
bwЭто я знаю. Но мне хотелось использовать единиый таймер и для консольного приложения и для любого GUI.
У GUI библиотек как правило есть свои таймеры.
bwПока только это нашёл.
Так же, по крайней мере это касается GTK/GLib, существует особенность использования потоков (еще по форуму про gobject, gtk и потоки).
Офлайн
> Однако, тема была создана сравнительно давно.
Здесь много глупых создается, так что фильтрую я достаточно грубо и интересные вопросы частенько пропускаю, увы.
> Пока только это нашёл.
Ну я и имел ввиду gobject.threads_init.
> Правильнее было бы
Иметь джина в ночном горшке :-).
..bw
Офлайн
Доделал вариант и с wxPython. В общем, таймер остался почти тот же, но в GUI-шной части пришлось ставить защиту. Интересно, что в моей системе (Ubuntu 9.10) погрешность таймера около 1 миллисекунды, при чём, большая часть этой погрешности - постоянная. Так что можно провести калибровку, которая повысит точность ещё в 10 раз.
import threading
class Timer:
def __init__(self, function):
self._func = function
self._timer = None
def start(self, period=1.0):
if self._timer == None:
self._timer = threading.Timer(period, function=self.on_timeout)
self._timer.start()
def on_timeout(self):
self._func()
self._timer.run()
def is_running(self):
return bool(self._timer)
def stop(self):
if self._timer:
self._timer.cancel()
self._timer = None
if __name__ == '__main__':
import wx
import datetime
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Timer with GUI",
size=(300, 600))
panel = wx.Panel(self)
startBtn = wx.Button(panel, -1, "Start timer")
stopBtn = wx.Button(panel, -1, "Stop timer")
clearBtn = wx.Button(panel, -1, "Clear")
self.log = wx.TextCtrl(panel, -1, "",
style=wx.TE_RICH|wx.TE_MULTILINE)
inner = wx.BoxSizer(wx.HORIZONTAL)
inner.Add(startBtn, 0, wx.RIGHT, 15)
inner.Add(stopBtn, 0, wx.RIGHT, 15)
inner.Add(clearBtn, 0, wx.RIGHT, 15)
main = wx.BoxSizer(wx.VERTICAL)
main.Add(inner, 0, wx.ALL, 5)
main.Add(self.log, 1, wx.EXPAND|wx.ALL, 5)
panel.SetSizer(main)
self.Bind(wx.EVT_BUTTON, self.on_start_button, startBtn)
self.Bind(wx.EVT_BUTTON, self.on_stop_button, stopBtn)
self.Bind(wx.EVT_BUTTON, self.on_clear_button, clearBtn)
self.Bind(wx.EVT_CLOSE, self.on_close_window)
self.timer = Timer(self.ret_func)
def on_start_button(self, evt):
self.timer.start(0.1)
def on_stop_button(self, evt):
self.timer.stop()
def on_clear_button(self, evt):
self.log.Clear()
def ret_func(self):
self.log.AppendText("timer: %s\n" %
datetime.datetime(2000, 1, 1).now())
def on_close_window(self, evt):
self.timer.stop()
self.Destroy()
app = wx.PySimpleApp()
frm = MyFrame()
frm.Show()
app.MainLoop()
Офлайн
Работа над ошибками. Написал про защиту в GUI, но не поставил. Кроме того, мой способ оживления таймера очень не надёжен. Лучше уж тупо воссоздавать поток. Такой вариант менее изящен, но вроде бы не глючит:
import threading
class Timer:
def __init__(self, function):
self._func = function
self._timer = None
self._period = 1.0
self._isRunning = False
def start(self, period):
if self._timer == None:
self._timer = threading.Timer(period, function=self.on_timeout)
self._timer.start()
self._period = period
self._isRunning = True
def on_timeout(self):
self._func()
self.stop()
self.start(self._period)
def is_running(self):
return self._isRunning
def stop(self):
if self._timer:
self._timer.cancel()
self._timer = None
self._isRunning = False
if __name__ == '__main__':
import wx
import datetime
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Timer with GUI",
size=(300, 600))
panel = wx.Panel(self)
startBtn = wx.Button(panel, -1, "Start timer")
stopBtn = wx.Button(panel, -1, "Stop timer")
clearBtn = wx.Button(panel, -1, "Clear")
self.log = wx.TextCtrl(panel, -1, "",
style=wx.TE_RICH|wx.TE_MULTILINE)
inner = wx.BoxSizer(wx.HORIZONTAL)
inner.Add(startBtn, 0, wx.RIGHT, 15)
inner.Add(stopBtn, 0, wx.RIGHT, 15)
inner.Add(clearBtn, 0, wx.RIGHT, 15)
main = wx.BoxSizer(wx.VERTICAL)
main.Add(inner, 0, wx.ALL, 5)
main.Add(self.log, 1, wx.EXPAND|wx.ALL, 5)
panel.SetSizer(main)
self.Bind(wx.EVT_BUTTON, self.on_start_button, startBtn)
self.Bind(wx.EVT_BUTTON, self.on_stop_button, stopBtn)
self.Bind(wx.EVT_BUTTON, self.on_clear_button, clearBtn)
self.Bind(wx.EVT_CLOSE, self.on_close_window)
self.timer = Timer(self.ret_func)
def on_start_button(self, evt):
self.timer.start(0.02)
def on_stop_button(self, evt):
self.timer.stop()
def on_clear_button(self, evt):
self.log.Clear()
def ret_func(self):
wx.CallAfter(self._ret_func)
def _ret_func(self):
if self.log.GetNumberOfLines() > 20:
self.log.Clear()
self.log.AppendText("timer: %s\n" %
datetime.datetime(2000, 1, 1).now())
def on_close_window(self, evt):
self.timer.stop()
self.Destroy()
app = wx.PySimpleApp()
frm = MyFrame()
frm.Show()
app.MainLoop()
Офлайн
По некоторым соображениям решил продолжить тему. У предложенного таймера есть недостаток - он “плывёт”. То есть, накапливает погрешности, вызванные несовершенством ОС и, возможно, также вызванные GIL. Так как (на первый взгляд) это плавание легко удалить, то я сделаю это.
import threading
import time
class Timer:
def __init__(self, function):
self._func = function
self._isRunning = False
def start(self, period, startWithJerk=False):
if self._isRunning == False:
self._currentTime = time.time()
self._start_new_period(period)
if startWithJerk: self._func()
self._period = period
self._isRunning = True
def _start_new_period(self, period):
self._timer = threading.Timer(period, function=self._on_timeout)
self._timer.start()
def _on_timeout(self):
if self._isRunning:
self._currentTime += self._period
period = self._period - (time.time() - self._currentTime)
while period <= 0:
period += self._period
self._start_new_period(period)
self._func()
def is_running(self):
return self._isRunning
def stop(self):
if self._isRunning:
self._timer.cancel()
self._timer.join()
self._timer = None
self._isRunning = False
if __name__ == '__main__':
import wx
import datetime
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Timer with GUI",
size=(300, 600))
panel = wx.Panel(self)
startBtn = wx.Button(panel, -1, "Start timer")
stopBtn = wx.Button(panel, -1, "Stop timer")
clearBtn = wx.Button(panel, -1, "Clear")
self.log = wx.TextCtrl(panel, -1, "",
style=wx.TE_RICH|wx.TE_MULTILINE)
inner = wx.BoxSizer(wx.HORIZONTAL)
inner.Add(startBtn, 0, wx.RIGHT, 15)
inner.Add(stopBtn, 0, wx.RIGHT, 15)
inner.Add(clearBtn, 0, wx.RIGHT, 15)
main = wx.BoxSizer(wx.VERTICAL)
main.Add(inner, 0, wx.ALL, 5)
main.Add(self.log, 1, wx.EXPAND|wx.ALL, 5)
panel.SetSizer(main)
self.Bind(wx.EVT_BUTTON, self.on_start_button, startBtn)
self.Bind(wx.EVT_BUTTON, self.on_stop_button, stopBtn)
self.Bind(wx.EVT_BUTTON, self.on_clear_button, clearBtn)
self.Bind(wx.EVT_CLOSE, self.on_close_window)
self.timer = Timer(self.ret_func)
def on_start_button(self, event):
self.timer.start(0.1)
def on_stop_button(self, event):
self.timer.stop()
def on_clear_button(self, event):
self.log.Clear()
def ret_func(self):
wx.CallAfter(self._ret_func)
def _ret_func(self):
if self.log.GetNumberOfLines() > 50:
self.log.Clear()
self.log.AppendText("timer: %s\n" %
datetime.datetime(2000, 1, 1).now())
def on_close_window(self, event):
self.timer.stop()
self.Destroy()
app = wx.PySimpleApp()
frm = MyFrame()
frm.Show()
app.MainLoop()
Офлайн
Не нужно перезапускать поток. Это очень накладно. threding.Timer сделан на питоне - вы тоже сможете повторить.
datetime.datetime.now - classmethod
Офлайн