views:

2758

answers:

11

Hi,

I'm searching for an elegant way to convert a normal Python dict with some nested dicts to an object.

For example:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

Should be accessible in this way:

>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
bar

I think, this is not possible without recursion, but what would be a nice way to get an objectstyle for dicts?

Thank you in advance, cheers Marc

+5  A: 
x = type('new_dict', (object,), d)

then add recursion to this and you're done.

edit this is how I'd implement it:

>>> d
{'a': 1, 'b': {'c': 2}, 'd': ['hi', {'foo': 'bar'}]}
>>> def obj_dic(d):
    top = type('new', (object,), d)
    seqs = tuple, list, set, frozenset
    for i, j in d.items():
     if isinstance(j, dict):
         setattr(top, i, obj_dic(j))
     elif isinstance(j, seqs):
         setattr(top, i, 
          type(j)(obj_dic(sj) if isinstance(sj, dict) else sj for sj in j))
     else:
         setattr(top, i, j)
    return top

>>> x = obj_dic(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'
SilentGhost
Please show how one could add recursion to this.
Aaron Digulla
thanks for your answer. but where in detail should i add recursion? to x? or should i implement __getattr__ and do lazy convert on request?
Marc
added recursion implementation.
SilentGhost
+1 for using type, kinda scary the number of answers duplicating functionality already built into the language
Mark Roddy
+1  A: 

x.__dict__.update(d) should do fine.

Alex
thanks for your answer, but what is x? the dict or a standard object? could you give me a hint please.
Marc
x is your object. Every object has a __dict__. By updating the __dict__ of a object you are actually updating the key vars in it.
Alex
The bold words are _ _ dict _ _
Alex
This won't handle nested dictionaries.
FogleBird
+3  A: 

This should get your started:

class dict2obj(object):
    def __init__(self, d):
        self.__dict__['d'] = d

    def __getattr__(self, key):
        value = self.__dict__['d'][key]
        if type(value) == type({}):
            return dict2obj(value)

        return value

d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

x = dict2obj(d)
print x.a
print x.b.c
print x.d[1].foo

It doesn't work for lists, yet. You'll have to wrap the lists in a UserList and overload __getitem__ to wrap dicts.

Aaron Digulla
To make it work for lists, use the `if isinstance(d, list)` clause from `Anon`'s answer.
Vinay Sajip
+3  A: 
>>> def dict2obj(d):
        if isinstance(d, list):
         d = [dict2obj(x) for x in d]
        if not isinstance(d, dict):
            return d
        class C(object):
         pass
        o = C()
        for k in d:
            o.__dict__[k] = dict2obj(d[k])
        return o


>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'
Anon
A: 

Let me explain a solution I almost used some time ago. But first, the reason I did not is illustrated by the fact that the following code:

d = {'from': 1}
x = dict2obj(d)

print x.from

gives this error:

  File "test.py", line 20
    print x.from == 1
                ^
SyntaxError: invalid syntax

Because "from" is a Python keyword there are certain dictionary keys you cannot allow.


Now my solution allows access to the dictionary items by using their names directly. But it also allows you to use "dictionary semantics". Here is the code with example usage:

class dict2obj(dict):
    def __init__(self, dict_):
        super(dict2obj, self).__init__(dict_)
        for key in self:
            item = self[key]
            if isinstance(item, list):
                for idx, it in enumerate(item):
                    if isinstance(it, dict):
                        item[idx] = dict2obj(it)
            elif isinstance(item, dict):
                self[key] = dict2obj(item)

    def __getattr__(self, key):
        return self[key]

d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

x = dict2obj(d)

assert x.a == x['a'] == 1
assert x.b.c == x['b']['c'] == 2
assert x.d[1].foo == x['d'][1]['foo'] == "bar"
Dawie Strauss
class Struct: def __init__(self, **entries): self.__dict__.update(entries)
Kenneth Reitz
+16  A: 
class Struct:
    def __init__(self, **entries): 
        self.__dict__.update(entries)

Then, you can use:

>>> args = {'a': 1, 'b': 2}
>>> s = Struct(**args)
>>> s
<__main__.Struct instance at 0x01D6A738>
>>> s.a
1
>>> s.b
2
Eli Bendersky
Very nice. I like this answer and having a named class for the converted object.
Great Turtle
+1 wow. You don't want to SEE the code that I can rip out of my app thanks to this.
Chris Lawlor
+11  A: 
class obj(object):
    def __init__(self, d):
        for a, b in d.items():
            if isinstance(b, (list, tuple)):
               setattr(self, a, [obj(x) if isinstance(x, dict) else x for x in b])
            else:
               setattr(self, a, obj(b) if isinstance(b, dict) else b)


>>> x = obj(d)
>>> x.b.c
2
>>> x.d[1].foo
'bar'
Nadia Alramli
Good! I'd replace .items() by .iteritems(), though, for a smaller memory footprint.
EOL
If not an OP requirement, this is not an issue - but note that this won't recursively process objects in lists within lists.
Anon
Nice! But then the dict needs to be complete when you create the class... I guess that works... you could create another instance if you modify the dict. Good stuff!
Kieveli
A: 

thank you for all your suggestions. i've put the together for comparison and benchmarked them. you can find the results here:

http://pastebin.org/10514

Marc
This test is not fair, the first solution will be more expensive on the long run.
Nadia Alramli
this may be absolutly true, but I havent got a more complex testcase available. I will test you solution in production with real data. the price for elegance is yours :)
Marc
Without getting particularly complex, you could do a similar test of speed of access w/o having creation in the loop - if that is of significance to your situation. (And vice versa with just creation timed.) You may find a tradeoff in creation speed vs. access time. (But then, too, there is performance vs. resulting memory requirements to consider.)
Anon
your pastebin entry has expired.
Great Turtle
+1  A: 

Here's another implementation:

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

def dict_to_obj(d):
    if isinstance(d, (list, tuple)): return map(dict_to_obj, d)
    elif not isinstance(d, dict): return d
    return DictObj(dict((k, dict_to_obj(v)) for (k,v) in d.iteritems()))

[Edit] Missed bit about also handling dicts within lists, not just other dicts. Added fix.

Brian
Note that setting __dict__ to the source dictionary means that any changes to attributes on the resulting object will also affect the dictionary that created the object, and vice-versa. This may lead to unexpected results if the dictionary is used for something other than creating the object.
Mark Roddy
@Mark: Actually, a new dictionary is being passed to DictObj every time, rather than just passing through the same dict object, so this won't actually occur. It's neccessary to do this, as I need to translate the *values* within a dictionary as well, so it would be impossible to pass through the original dict object without mutating it myself.
Brian
A: 

Here is another way to implement SilentGhost's original suggestion:

def dict2obj(d):
  if isinstance(d, dict):
    n = {}
    for item in d:
      if isinstance(d[item], dict):
        n[item] = dict2obj(d[item])
      elif isinstance(d[item], (list, tuple)):
        n[item] = [dict2obj(elem) for elem in d[item]]
      else:
        n[item] = d[item]
    return type('obj_from_dict', (object,), n)
  else:
    return d
Roberto Liffredo
+1  A: 

Building off what i did for this question:

class data(object):
    def __init__(self,*args,**argd):
        self.__dict__.update(dict(*args,**argd))

def makedata(d):
    d2 = {}
    for n in d:
        d2[n] = trydata(d[n])
    return data(d2)

def trydata(o):
    if isinstance(o,dict):
        return makedata(o)
    elif isinstance(o,list):
        return [trydata(i) for i in o]
    else:
        return o

You call makedata on the dictionary you want converted (or maybe trydata depending on what you expect as input) and it spits out a data object.

Notes:

  • You can add elifs to trydata if you need more funtionality
  • Obviously this won't work if you want x.a = {} or similar
  • If you want a readonly version, use the class data from the original answer
David X