2

I have a class with a dict attribute, like this :

class MyClass:

    def __init__(self):
        self.mydict = {'var1': 'value1', 'var2': 'value2', ...}

When I want to get the values, I have to do this :

cls = MyClass()
print(cls.mydict['var1'])
print(cls.mydict['var2'])

What is the solution to get the values directly in attributes please :

cls = MyClass()
print(cls.var1)
print(cls.var2)
Mazdak
  • 105,000
  • 18
  • 159
  • 188
ncrocfer
  • 2,542
  • 4
  • 33
  • 38

6 Answers6

4

Using setattr, you can set attribute dynamically:

>>> class MyClass:
...     def __init__(self):
...         mydict = {'var1': 'value1', 'var2': 'value2'}
...         for key, value in mydict.items():
...             setattr(self, key, value)
...
>>> instance = MyClass()
>>> instance.var1
'value1'
>>> instance.var2
'value2'
falsetru
  • 357,413
  • 63
  • 732
  • 636
4

You could add an additional function to the class that will be able to parse the dict and insert the relevant attributes:

def assign_variables( self ):
  for key, val in self.mydict.items():
    setattr( self, key, val )

I'm using the built-in setattr() function here to set attributes with dynamic names/values:

This is the counterpart of getattr(). The arguments are an object, a string and an arbitrary value. The string may name an existing attribute or a new attribute. The function assigns the value to the attribute, provided the object allows it.
For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123.

You can call this function inside your constructor after the mydict variable is defined or even just place the loop in the constructor.

Lix
  • 47,311
  • 12
  • 103
  • 131
  • In this case you now have two object references for each member of `mydict` stored in the class object instance. Seems like it would be better to only have one. This method could be changed in a very minor way to avoid this (just avoid `self.mydict` altogether by passing `mydict` to `assign_variables`). – Rick Dec 09 '14 at 11:55
  • 1
    @RickTeachey - correct. One would then have to ask why the dict needs to exist as an attribute if it's pre-defined and not simply assign the class attributes as-is without this conversion code. But then we'd be getting into the [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Lix Dec 09 '14 at 11:58
  • 1
    I was assuming it is pre-defined as a simplification of the problem presentation. Can't think of a reason you'd actually want to do it this way in practice. – Rick Dec 09 '14 at 12:06
2

Another solution is to implement __getattr__:

class MyClass(object):
    def __init__(self):
        self.mydict = {'var1': 'value1', 'var2': 'value2', ...}

    def __getattr__(self, name):
        try:
            return self.mydict[name]
        except KeyError:
            raise AttributeError("object %s has no attribute '%s'" % (self, name))
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Raising a new error within the `except` statement like this seems to mess up the traceback - there are two traces, which is pretty confusing. Isn't there a better way? – Rick Dec 09 '14 at 11:52
  • @RickTeachey: not sure what python shell or python implementation you're using, but this code works as it should on all CPython versions I can remember (which means since 1.5.something). – bruno desthuilliers Dec 09 '14 at 13:13
  • It works fine, but it produces the message `During handling of the above exception, another exception occurred:`. Might be my inexperience, but it doesn't seem very clean, and just doesn't seem like a very good way of doing things. `if name in self.mydict: return self.mydict[name]`, `else: raise AttributeError()` seems better. Or even: `try:` (etc etc), `except KeyError: pass`, `raise AttributeError()`. – Rick Dec 09 '14 at 14:47
  • 1
    Must be either a Python 3.x thing or your shell. And catching an exception to raise another one (from the except clause) is a very common idiom. – bruno desthuilliers Dec 09 '14 at 15:42
  • I am using 3.x in Anaconda (just plain Windows CMD). – Rick Dec 10 '14 at 10:59
  • Aha! PEP 415 describes the proper way to do this in Python 3.3, avoiding the messy traceback issues I described above. The last line should read `raise AttributeError("object %s has no attribute '%s'" % (self, name)) from None` – Rick Dec 15 '14 at 14:14
0

You have to add __repr__ method in your class to get required output.

ChrisGPT was on strike
  • 127,765
  • 105
  • 273
  • 257
nothing
  • 1
  • 1
-1

You can directly update the __dict__ attribute of your object I believe:

class MyClass:

    def __init__(self):
        self.mydict = {'var1': 'value1', 'var2': 'value2', ...}
        self.__dict__.update(self.mydict)
LexyStardust
  • 1,018
  • 5
  • 18
  • 1
    *Warning*: this solution totally bypasses `__setattr__()` and all descriptors (properties or else) and can break quit-e a few things. – bruno desthuilliers Dec 08 '14 at 14:06
  • Is it not the case that object's `"__setattr__()"` basically assigns directly to `__dict__` anyway? (Genuine question, not being contrary). In this example the OP isn't inheriting anything so would this be any less safe than using `__setattr__()`? – LexyStardust Dec 08 '14 at 14:43
  • 2
    `object.__setattr__()` takes care of descriptors and slots - assigning to `__dict__` is only done if there's no binding descriptor by the same name AND if the class uses a `__dict__` (=> doesn't use slots). Also you can have a customised `__setattr__` somewhere in the class hierarchy. So yes, in this case (well, for what we can see of the code, that is) you can say it's "safe" - until someone tries to add descriptors etc... To make a long story short: only use this solution if your class is of the brainless data-bag kind (iow a dict with dotted syntax). – bruno desthuilliers Dec 08 '14 at 15:02
  • As others have noted: tons of pitfalls for this method, and there's really no reason to use it when you can just as easily use `for k, v in self.mydict.items(): self.__setattr__(k,v)`. – Rick Dec 08 '14 at 16:00
-1

Another way would be to override __getattr__ and __setattr__ together, which avoids having two object references to the same attribute in the class instance object (one O.R. to value1 inside myobj.mydict['var1'], and another O.R. to value1 in myobj.__dict__['var1']):

class MyClass():
    def __init__(self):
        self.__dict__['_mydict'] = {'var1': 'value1', 'var2': 'value2'}
    #Note: using the @property (descriptor) is optional. I think it makes 
    #things more convenient.
    @property
    def mydict(self):
        return self._mydict
    #NOTE: __getattr__ won't get called if att is found in self.__dict__ first! 
    def __getattr__(self,att):
        if att in self.mydict:       # or without using @property: if att in self._mydict:
            return self.mydict[att]  #                                  return self._mydict[att]
        else:
            raise AttributeError("'{n}' object has no attribute '{a}'".format(n = type(self).__name__, a = att))
    def __setattr__(self,att,val):
        super().__setattr__(att, val)
        if att in self.mydict:
            self.mydict[att] = val   # or without using @property: self._mydict[att] = val
            self.__delattr__(att)    # remove duplicate copy of object reference to att

Note that doing it this way means you cannot add more key,value pairs to mydict unless you call the property directly, e.g.:

myobj = MyClass()
myobj.x = 1
myobj.mydict #note that result does not contain an entry for 'x'
myobj.mydict['x'] = 2
myobj.mydict #now it does

Also note that getting and deleting the mydict members will be overridden by any existing attribute of the same name (not only that, but you can't delete mydict members at all unless you also override __delattr__ to enable this behavior):

#continuing the above code session
assert myobj.x == 1 #myobj.x is coming from self.x, not from self._mydict['x']
myobj.var1 = 'a new value'
myobj.var1 #results in 'a new value' as expected, but where is it coming from? 
assert myobj.mydict['var1'] == 'a new value' #Ah: it's coming from self._mydict as expected
assert myobj.__dict__['var1'] #ERROR, as expected
del myobj.x
assert myobj.x == 2 #now myobj.x is coming from self._mydict['x'], self.x having been deleted
del myobj.var1 #raises AttributeError; attributes in mydict can't be deleted (unless you also override __delattr__

If you want to change this behavior, you have to override __getattribute__ (EDIT: which, as bruno desthuilliers notes below, is usually not a good idea).

Rick
  • 43,029
  • 15
  • 76
  • 119
  • Your implementation of `__setattr__` does not respect the descriptor protocol. As a general rule, overridding `__setattr__` and `__getattribute__` is seldom a good idea - it can be tricky and can lead to poor performances. – bruno desthuilliers Dec 09 '14 at 08:17
  • First of all: I *didn't* override `__getattribute__`, but `__getattr__`. I only said you'd have to do that in order to look inside `mydict` first. If that's the primary reason for the downvote, I submit it's unwarranted. Second: How does it not respect the descriptor protocol? The get method is called using `[]`, and something is done with its return. That's what descriptors are for, is it not? If I'm wrong please point me somewhere so I can learn about what you mean. In any case, the descriptor is incidental, making things more convenient; it's still a useful solution to the OP's problem. – Rick Dec 09 '14 at 12:12
  • In light of my inexperience, and trust in your judgment as an obviously much more experienced person, I have edited my answer to make it clear the descriptor is optional. – Rick Dec 09 '14 at 12:18
  • I'm not talking about `__getattr__` - which is optional and only invoked (if defined) as a last resort, but about `__setattr__` which (together with `__getattribute__`) implement the attribute resolution rules. And I think you don't really get the point about descriptors - it's not about adding a `mydict` property, it's about looking the class and mro for binding descriptors (descriptors that implement `__set__`) and invoking them _before_ falling back to instance storage. – bruno desthuilliers Dec 09 '14 at 13:07
  • I know it's just a comment so it just isn't the space for a lengthy explanation, but I still don't understand the specific problem that is being caused by using a `dict` descriptor in the manner above. As for `__setattr__`, you have a point and I remember now the correct way to do this. Have edited accordingly. – Rick Dec 09 '14 at 13:28
  • once again it has *nothing* to do with your `mydict` property, it's about your implementation of `__setattr__` that doesn't do it's job correctly, ie *first* looking for binding descriptors in the class and it's mro, and invoking them if found. Your "corrected" implementation is better but it's still broken - descriptors must be looked up *before* instance storage. – bruno desthuilliers Dec 09 '14 at 13:35
  • i think i understand what you're saying. not sure if the latest edit is an improvement or not. what do you think? – Rick Dec 09 '14 at 13:42
  • 1
    I thinks this is a good example of why overridding `__setattr__` is usually not a good idea - unless you have a really compelling reason to so, that is, and then you'd better do it right (which is still not the case in your last edit - if you have a binding descriptor by the same name as a key in `mydict`, you *DONT* want to update `mydict` nor `del` this attribute). – bruno desthuilliers Dec 09 '14 at 15:50
  • This has been a helpful conversation. I'd love to see an example of a correctly overridden `__setattr__` that doesn't violate/cause these sorts of considerations/problems. Surely it is possible. – Rick Dec 09 '14 at 16:00
  • Well yes it is of course possible - but it can be tricky... The C source code (object.c and typeobject.c) might be a good start. – bruno desthuilliers Dec 09 '14 at 16:29
  • I'll do that. It would be pretty great if you did a self-answered question on this topic since you seem to have a lot to say about it. As a part of that I'd also be interested in sample code showing how my implementation above is broken. Thanks for the back and forth. – Rick Dec 09 '14 at 21:00