Reputation: 59
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.
Upvotes: 2
Views: 7815
Reputation: 296
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.
Upvotes: 0
Reputation: 5039
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
Upvotes: 0
Reputation: 77892
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'])
Upvotes: 4
Reputation: 9806
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.
Upvotes: 1