I have a dataclass for a set of timers. Each instance of the dataclass can have a different number of timers. The number of timers in a specific instance is fixed but can vary between instances. I'd like to get the number of timers in a specific class using __len__. How can I specify the return value of __len__ with out resorting to a magic number like in my demo code? I'd rather it be generated programmatically when __len__ is called so there's no possibility of a maintenance error.
Here's my code:
@dataclass
class SSTimers():
def tic(self):
pass
def __iter__(self):
return iter([])
@dataclass
class Timers(SSTimers):
intervalTimer = tmr.Timer(0)
vSafetyPTimer = tmr.Timer(0)
aRefractoryTimer = tmr.Timer(0)
aBlankingTimer = tmr.Timer(0)
vRefractoryTimer = tmr.Timer(0)
vBlankingTimer = tmr.Timer(0)
def tic(self):
self.intervalTimer.tic()
self.vSafetyPTimer.tic()
self.aRefractoryTimer.tic()
self.aBlankingTimer.tic()
self.vRefractoryTimer.tic()
self.vBlankingTimer.tic()
def __len__(self):
return 6
def __iter__(self):
return iter([self.intervalTimer, self.vSafetyPTimer, self.aRefractoryTimer, self.aBlankingTimer, self.vRefractoryTimer, self.vBlankingTimer ])
I'd like len to return something like len(__iter__) if that makes any sense.
The good thing about dataclasses is that they are plain Python classes, instrumented witha decorator - so normal class mechanisms like __len__ and __iter__ will work as planed, as you had found out.
Since you are using a superclass, and your Timer fields have something in common - the Timer suffix, as far as you document that, you can add all the instrumentation code straight in your superclass.
Also, in your example, you are really not using anything from dataclasses: the fields have to get annotations, so they are proper marked as fields and get the benefits provided by the dataclass shortcuts - I did that in the snippet bellow, otherwise the fields call would return empty.
Dataclasses in the stdlib have their field function for introspection of the declared fields - from there we can instrospect the Timer fields directly in a method, with minimal fuzz.
Also, as placed in the comments: you are creating timer instances as class attributes, so every instance of your dataclass would share the same Timer objects - that is most likely not your intended purpose. Dataclasses have a way to create a new instance of a class for each instance of a data class using the Field helper.
Perceive that if your real use case Timer fields don't have the naming in common, after retrieving the field in .timerfields you can do any other check you want to ensure it is one you want to include in the count.
from dataclasses import dataclass, fields # please when posting code on S.O. include the import statementes, so that people can paste your code and run it.
# MOCK tmr module and tmr.Timer class so that the example snippet runs:
class tmr:
class Timer():
def __init__(self, start=0):
pass
@dataclass
class SSTimers():
def tic(self):
pass
@property
def _timerfields(self):
for field in fields(self):
if field.name.endswith("Timer"):
yield field.name
def __iter__(self):
return (getattr(self, field) for field in self._timerfields)
def __len__(self):
return len(list(self._timerfields))
def tic(self):
for fieldname in self._timerfields:
field = getattr(self, fieldname)
field.tic()
@dataclass
class Timers(SSTimers):
intervalTimer: tmr.Timer = Field(default_factory=lambda: tmr.Timer(0))
vSafetyPTimer: tmr.Timer = Field(default_factory=lambda: tmr.Timer(0))
aRefractoryTimer: tmr.Timer = Field(default_factory=lambda: tmr.Timer(0))
aBlankingTimer: tmr.Timer = Field(default_factory=lambda: tmr.Timer(0))
vRefractoryTimer: tmr.Timer = Field(default_factory=lambda: tmr.Timer(0))
vBlankingTimer: tmr.Timer = Field(default_factory=lambda: tmr.Timer(0))