I have a piece of hardware I write a class for which has multiple inputs. Each input (channel) has a name, so I created a list:
CHANNELS = {"T0": 0, "Tend": 1, "A": 2, "B": 3, "C" : 4, "D" : 5,
"E" : 6, "F" : 7, "G" : 8, "H" : 9}
Now I would like to create a property to access the value of each channel:
@property
def readT0(self)
return self._query("REQUEST STRING 0")
# the request string expects a number, instead of the real name T0
@property
def readTend(self)
return self._query("REQUEST STRING 1")
etc.
I would rather do something like this (less source code):
def read(self, channel)
return self._query("REQUEST STRING %s" % channel)
and use some kind of translation to create the attributes:
def __init__ bla bla:
bla bla
for x in CHANNELS:
setattr(self, "read" + str(x), self.read(CHANNELS[x])
so
class.readA # channel A value: 10
> 10
class.readT0 # channel T0 value: 0.11
> 0.11
This way, if the hardware uses more channels, I can just add to the CHANNELS
dictionary. Or is there a better way?
Since I only want to read values, I would stop here. But is there a way to combine this with a setter, too?
edit:
I have to clarify: I don't want to change the dictionary or access the values of the dictionary at runtime, I want to use it on class creation to create multiple attributes for a hardware to read the hardware values.
The hardware is a ADC with channels. I can read the ADC value of each channel with
someclass._query("REQUEST STRING i")
# i is the channel number and returns the ADC value (e.g. 10.45 V)
If you really want to dynamically create functions and make them members of your class instances, you can use lambda
:
CHANNELS = {"T0": 0, "Tend": 1, "A": 2, "B": 3, "C" : 4, "D" : 5,
"E" : 6, "F" : 7, "G" : 8, "H" : 9}
class Foo(object):
def __init__(self):
for x in CHANNELS:
setattr(self, "read{}".format(x), lambda x=x: self.read(CHANNELS[x]))
def read(self, channel):
return self._query("REQUEST STRING {}".format(channel))
def _query(self, q):
print "query {}".format(q)
f = Foo()
f.readT0()
f.readTend()
f.readA()
f.readB()
f.readC()
It works but there are a few drawbacks:
- it creates the same set of functions for each and any instance (for no reason in this case since all instances are based on the same
CHANNELS
definition)
- these functions are not documented and won't appear in
dir(Foo)
or help(Foo)
jonsharpe's __getattr__
solution solves the first point but not the second. The simplest solution here is to use a class decorator that will add the getters (either as methods or properties, it's up to you) on the class itself, ie (properties version):
def with_channel_props(cls):
for x in cls.CHANNELS:
getter = lambda self, x=x: self.read(self.CHANNELS[x])
setattr(cls, "{}".format(x), property(getter))
return cls
@with_channel_props
class Baaz(object):
CHANNELS = {
"T0": 0, "Tend": 1, "A": 2, "B": 3, "C" : 4, "D" : 5,
"E" : 6, "F" : 7, "G" : 8, "H" : 9
}
def read(self, channel):
return self._query("REQUEST STRING {}".format(channel))
def _query(self, q):
return "query {}".format(q)
b = Baaz()
print b.T0
print b.Tend
print b.A
print b.B
print b.C
Now you can use dir(Baaz)
and help(Baaz)
or any other introspection mechanism.
I think this is what you want:
class Something(object):
CHANNELS = {"T0": 0, "Tend": 1, "A": 2, "B": 3, "C": 4, "D": 5,
"E": 6, "F": 7, "G": 8, "H": 9}
def __getattr__(self, name):
"""Handle missing attributes."""
if name.startswith('read') and name[4:] in self.CHANNELS:
return self.CHANNELS[name[4:]]
return super(Something, self).__getattribute__(name)
In use:
>>> thing = Something()
>>> thing.readA
2
>>> thing.readT0
0
>>> thing.garbage
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in __getattr__
AttributeError: 'Something' object has no attribute 'garbage'
You can then use these channel attributes in your call to _query
as required. If you want to assign to the dictionary too, you'd need to implement __setattr__
.