-->

Create properties in class from list / attribute i

2019-09-15 03:59发布

问题:

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)

回答1:

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.



回答2:

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__.