Question

I am using a dict object to hold instance values in a class. I like having the dict because I often want to set or get a bunch of values at the same time and it is easy to pass a list or dict to accomplish this. For example to get several of the instance values I can just pass a [list of keys] and get back a {dict of those keys: values}. As far as I can tell, doing that with separate attributes for each key requires jumping through a lot of code hoops if it is possible at all.

Anyway, I'm looking for something like this:

class SomeClass(object):
    def __init__(self):
        self._desc = {
            'hair': 'brown'
            'eyes': 'blue'
            'height': 1.55
        }

This is all pretty simple up to here, but some of the values require special "set" conditions. My first attempt was something like this:

class SomeClass(object):
    def __init__(self):
        self._haircolor = 'blonde'
        self._eyecolor = 'green'
        self._desc = {
            'haircolor': self.haircolor
            'eyecolor': self.eyecolor
            'height': 1.55
        }

    @property
    self.haircolor(self):
        return self._haircolor

    @haircolor.setter
    self.haircolor(self, color):
        if color.lower() in ['blonde', 'brunette', 'brown', 'black', 'auburn', 'red']:
            self._haircolor = color
        else: raise ValueError()

    # Same type of property construct for eyecolor

This did not work. self._desc['haircolor'] would initialize to blonde, but it had no dynamic tie to the property construct or the self._haircolor attribute. I tried replacing this with

class SomeClass(object):
    def __init__(self):
        self._haircolor = 'blonde'
        self._eyecolor = 'green'
        self._desc = {
            'haircolor': property(self.get_haircolor, self.set_haircolor)
            'eyecolor': property(self.get_eyecolor, self.set_eyecolor)
            'height': 1.55
        }

    self.get_haircolor(self):
        return self._haircolor

    self.set_haircolor(self, color):
        if color.lower() in ['blonde', 'brunette', 'brown', 'black', 'auburn', 'red']:
            self._haircolor = color
        else: raise ValueError()

But this failed in a different way.

print self._desc['haircolor'] 

Would return a str for the property object, and

self._desc['haircolor'] = 'brunette'

destroyed the property object and replaced it with the string.

I researched a few similar questions, like this one and this one.

Both of those cases operate at the dictionary level however. The problem there is that I need to know a lot about the dict when I am defining the custom dict class because I need to anticipate key names and switch them in setitem (first example) or the dict-level property object (second example). Ideally I would like to associate the setter with the value object itself so the dict does not need to be aware that there is something special about the values a key can reference.

Was it helpful?

Solution

Python objects already store attributes in a dict so what is the point of using yet another dict here ? Just store your attributes the usual way (using properties where it makes sense), and build the desc dict dynamically when you need it.

class SomeClass(object):

    HAIRCOLORS = set(['blonde', 'brunette', 'brown', 'black', 'auburn', 'red'])
    EYECOLORS = set(['green', 'blue', 'brown', 'gray'])

    def __init__(self, haircolor="blond", eyecolor="green"):
        self.haircolor = haircolor
        self.eyecolor = eyecolor
        self.height = 1.55 # you didn't mention any validation here

    @property
    def haircolor(self):
        return self._haircolor

    @haircolor.setter
    def haircolor(self, color):
        color = color.lower()
        if color not in self.HAICOLORS:
            raise ValueError("'%s' is not a valid hair color" % color)
        self._haircolor = color

    # same thing for eyecolor

    @property
    def desc(self):
        return dict(
           haircolor=self.haircolor, 
           eyecolor=self.eyecolor, 
           height=self.height)

If this is a reccuring pattern you can eventually write your own descriptor:

class ChoiceDescriptor(object):
    def __init__(self, key, choices):
        self.key = key
        self.storage = "_%s" % key
        self.choices = set(choices)

    def __get__(self, instance, cls=None):
        if instance is None:
            return self
        return getattr(instance, self.storage)


    def __set__(self, instance, value):
        value = value.lower()
        if value not in self.choices:
            raise ValueError(
                "'%s' is not a valid value for '%s'" % (value, self.key))
        setattr(instance, self.storage, value)


class SomeClass(object):
    haircolor = ChoiceDescriptor("haircolor", 
        ['blonde', 'brunette', 'brown', 'black', 'auburn', 'red'])
    eyecolor = ChoiceDescriptor("eyecolor", 
        ['green', 'blue', 'brown', 'gray'])

OTHER TIPS

If you use self._desc['haircolor'] to get/set its value, it will invoke the __getitem__()/__setitem__() method of the dict no matter whether the value is a descriptor or not.

If you want to visit a descriptor, the way to visit it is to use something like this instance.descrip because at this time it will invoke __getattribute__() method and if descrip is a descriptor, it will use the corresponding setter and getter methods, otherwise it will treat it a normal attribute. Here's a doc about descriptor

You just want to control how a key is called in a dict? You may refer to this question Python: How to “perfectly” override a dict to override the __getitem__() and __setitem__() method in a dict-like class. This is not complex and all you should do is to create a class like that and then use it as if it's a dict type variable.
After that, use the new dict-like class to generate self._desc variable and getting or setting something like self._desc['haircolor'] will do as you desire.

As said before: Python objects already store attributes in a dict, so you can proxy it in "desc".

class SomeClass(object):
    def __init__(self, d):
        self.__dict__ = d

    @property
    def desc(self):
        return self.__dict__

also you can inherit from this class, to get the objects updated:

class objdict(dict):
    def __getattr__(self, name):
        if name in self:
            return self[name]
        else:
            raise AttributeError("No such attribute: " + name)

    def __setattr__(self, name, value):
        self[name] = value

    def __delattr__(self, name):
        if name in self:
            del self[name]
        else:
            raise AttributeError("No such attribute: " + name)

or use named tuples

I had a similar case and managed it using a descriptor:

class Description(object):
    """ Descriptor which gets/sets value from instances _desc attribute via key."""

    def __init__(self,key):
        self.key = key

    def __get__( self, instance, owner ):
        return instance._desc[self.key]

    def __set__( self, instance, value ):

        if self.key in instance._constraints:
            if value in instance._constraints[self.key]:
                instance._desc[self.key] = value
            else:
                print('Not allowed.')

        else:
            instance._desc[self.key] = value

class Person(object):

    _constraints = {'hair':['brown','blonde']}

    def __init__(self):
        self._desc = {
            'hair': 'brown',
            'eyes': 'blue',
            'height': 1.55,
        }

    hair = Description('hair')
    eyes = Description('eyes')
    height = Description('height')

Now we can:

p = Person()

p.hair = 'purple'

Will print 'Not allowed.' since the value 'purple' doesnt meet the constraints.

p.eyes = 1

Sets 'eyes' to 1.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top