I'm a relative noob to Python. On a range of 0 to 10, where 0 is a complete noob and 9 is Guido, I'd put myself as 1 aspiring to 2.
So I wanted a timer, rather like the Visual Basic object, and I wanted it with no cummulative error. And I wanted it flexible, so I wouldn't have to write another one. I'm very lazy BTW, so lazy that I will go to great lengths to avoid having to write something a second time. Actually, this fits quite well with the "write once, refer often" coding style. I liked the flexibility of the .config() setup style used for Tk controls, so wanted to implement that.
I spent a long time searching for timers, and there seemed to be about 3 basic types, I eventually settled on the threading.Timer repeat call model. I spent far too long battling with freezes in IDLE, before realising that my timers appeared to run OK standalone, and that IDLE just didn't like threads (I've just installed IPython, and will see how that copes once I've learnt my way round it).
Then I tried to get a flexible configuration, and that's where code bloat seemed to set in. I've tried to manage it back again by making lists of my attributes, and reusing those lists wherever I need them, I hope this class could serve as a pattern for any future classes with the minimum of editting. I understand the duck typing principle of try rather than test, but I prefer to have errors caught at the time I try to configure something, rather than later at the time I try to use it. I've tried to be as duck-like in my tests as possible, hoping to get the best of both worlds. I am not suggesting my tests are bomb-proof yet, I will test them more thoroughly in due course. My test for valid integers isn't quite as smart as I'd want it yet (accept 4, 0x11, '4', '0x11', reject 3.142) (int() covers enough of that ground for the moment), but that is a simpler issue to be tackled later.
So my concerns are:
Before I use it as a pattern for other classes, have I done a reasonably pythonic job, or am I just kidding myself?
There seem to be a lot of lines of support, and very little payload, could the same effect have been acheived more efficiently?
In searching for timers, I've seen a lot of comments bemoaning the fact that Python libraries don't have a standard repeat timer. Is anybody going to run into trouble using this one?
Any other comments
Thanks for your time to slog through what seems like a lot of code.
import time, threading
class Pacer():
""" A Pacer object can be configured at instantiation,
using config(kwargs), or at start(kwargs)
Call Pacer_obj.config() with no args to get a list of valid kwargs
It calls func_tick every period, with non-cummulative error (if possible)
Set max_ticks or max_overruns to zero to disable them
"""
def __init__(self,**kwargs):
self.zpint_keys=['max_overruns','max_ticks']
self.pfloat_keys=['period']
self.func_keys=['func_tick','func_done','func_over']
self.private_keys=['N_ticks','N_overruns','t_next_tick']
for key in self.zpint_keys:
setattr(self,key,0)
for key in self.pfloat_keys:
setattr(self,key,1)
for key in self.func_keys:
setattr(self,key,None)
for key in self.private_keys:
setattr(self,key,0)
self.config(**kwargs)
def spill(self):
print
for key in self.zpint_keys+self.pfloat_keys+self.func_keys+self.private_keys:
print key, '=',getattr(self,key)
print
def start(self,**kwargs):
self.config(**kwargs)
self.N_ticks=0
self.N_overruns=0
self.t_next_tick=time.time()+self.period
self.t=threading.Timer(self.period,self.tick)
self.t.start()
def tick(self):
if self.func_tick:
self.func_tick()
else:
print "you do realise you haven't defined a tick callback, don't you"
self.N_ticks += 1
self.t_next_tick += self.period
# have we reached maximum number of ticks?
if (self.N_ticks >= self.max_ticks) and (self.max_ticks != 0):
if self.func_done:
self.func_done()
else:
print 'quit on max ticks, no callback defined'
return # quit without scheduling another tick
# OK, so still ticking
# how long till next, with non-cummulative error
time2wait=self.t_next_tick-time.time()
if time2wait <= 0: # damn, we've overrun
time2wait=0 # set to least time possible
self.N_overruns += 1 # how many has that been?
if (self.N_overruns >= self.max_overruns) and (self.max_overruns != 0):
if self.func_over:
self.func_over()
else:
print 'quit on too many missed schedules, no callback defined'
return # quit without scheduling another tick
# OK, so *still* ticking
self.N_overruns=0 # reset the overrun counter
self.t=threading.Timer(time2wait+0.001,self.tick)
self.t.start()
def stop(self):
self.t.cancel() # and really nothing else needs to happen here
# it stops the next tick from happening
# which stops everything else
def config(self,**kwargs):
if not kwargs:
usage={'non-neg integers':self.zpint_keys,
'positive floats':self.pfloat_keys,
'callback functions':self.func_keys}
return usage
for key in self.zpint_keys:
if key in kwargs:
keyval=kwargs.pop(key)
try:
val=int(keyval)
if val<0:
print 'parameter ',key, ' must be zero or positive'
break
except:
print 'parameter ',key,' must be an integer'
break
setattr(self,key,val)
for key in self.pfloat_keys:
if key in kwargs:
keyval=kwargs.pop(key)
try:
val=float(keyval)
if val <= 0:
print 'parameter ',key,' must be positive'
break
except TypeError:
print 'parameter ',key,' must be a float'
break
setattr(self,key,val)
for key in self.func_keys:
if key in kwargs:
keyval=kwargs.pop(key)
if not callable(keyval):
print 'parameter ',key,' must be callable function'
break
setattr(self,key,keyval)
if kwargs:
print 'unknown parameter(s) were supplied to Pacer.config()'
print kwargs
if __name__=='__main__':
def hello():
print 'hello world'
q=Pacer()
q.spill()
b=q.config()
print b
q.config(max_ticks=7.5)
q.spill()
q.config(interloper=3)
q.config(func_tick=hello,max_ticks=3)
q.spill()
q.start()
time.sleep(5)
a=raw_input('press return to quit - ')