Convert Python dict to object?

匿名 (未验证) 提交于 2019-12-03 02:10:02

问题:

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?

回答1:

Update: In Python 2.6 and onwards, consider whether the namedtuple data structure suits your needs:

>>> from collections import namedtuple >>> MyStruct = namedtuple('MyStruct', 'a b d') >>> s = MyStruct(a=1, b={'c': 2}, d=['hi']) >>> s MyStruct(a=1, b={'c': 2}, d=['hi']) >>> s.a 1 >>> s.b {'c': 2} >>> s.c Traceback (most recent call last):   File "<stdin>", line 1, in <module> AttributeError: 'MyStruct' object has no attribute 'c' >>> s.d ['hi'] 

The alternative (original answer contents) is:

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 


回答2:

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)  >>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]} >>> x = obj(d) >>> x.b.c 2 >>> x.d[1].foo 'bar' 


回答3:

Surprisingly no one has mentioned Bunch. This library is exclusively meant to provide attribute style access to dict objects and does exactly what the OP wants. A demonstration:

>>> from bunch import bunchify >>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]} >>> x = bunchify(d) >>> x.a 1 >>> x.b.c 2 >>> x.d[1].foo 'bar' 

A Python 3 library is available at https://github.com/Infinidat/munch - Credit goes to codyzu



回答4:

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' 


回答5:

For anyone who happens to stumble upon this question nowadays. In Python 2.6+ there's a collection helper called namedtuple, that can do this for you:

from collections import namedtuple  d_named = namedtuple('Struct', d.keys())(*d.values())  In [7]: d_named Out[7]: Struct(a=1, b={'c': 2}, d=['hi', {'foo': 'bar'}])  In [8]: d_named.a Out[8]: 1 


回答6:

Taking what I feel are the best aspects of the previous examples, here's what I came up with:

class Struct:   '''The recursive class for building and representing objects with.'''   def __init__(self, obj):     for k, v in obj.iteritems():       if isinstance(v, dict):         setattr(self, k, Struct(v))       else:         setattr(self, k, v)   def __getitem__(self, val):     return self.__dict__[val]   def __repr__(self):     return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for       (k, v) in self.__dict__.iteritems())) 


回答7:

class Struct(object):     """Comment removed"""     def __init__(self, data):         for name, value in data.iteritems():             setattr(self, name, self._wrap(value))      def _wrap(self, value):         if isinstance(value, (tuple, list, set, frozenset)):              return type(value)([self._wrap(v) for v in value])         else:             return Struct(value) if isinstance(value, dict) else value 

Can be used with any sequence/dict/value structure of any depth.



回答8:

If your dict is coming from json.loads(), you can turn it into an object instead (rather than a dict) in one line:

import json from collections import namedtuple  json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values())) 

See also How to convert JSON data into a Python object.



回答9:

If you want to access dict keys as an object (or as a dict for difficult keys), do it recursively, and also be able to update the original dict, you could do:

class Dictate(object):     """Object view of a dict, updating the passed in dict when values are set     or deleted. "Dictate" the contents of a dict...: """      def __init__(self, d):         # since __setattr__ is overridden, self.__dict = d doesn't work         object.__setattr__(self, '_Dictate__dict', d)      # Dictionary-like access / updates     def __getitem__(self, name):         value = self.__dict[name]         if isinstance(value, dict):  # recursively view sub-dicts as objects             value = Dictate(value)         return value      def __setitem__(self, name, value):         self.__dict[name] = value     def __delitem__(self, name):         del self.__dict[name]      # Object-like access / updates     def __getattr__(self, name):         return self[name]      def __setattr__(self, name, value):         self[name] = value     def __delattr__(self, name):         del self[name]      def __repr__(self):         return "%s(%r)" % (type(self).__name__, self.__dict)     def __str__(self):         return str(self.__dict) 

Example usage:

d = {'a': 'b', 1: 2} dd = Dictate(d) assert dd.a == 'b'  # Access like an object assert dd[1] == 2  # Access like a dict # Updates affect d dd.c = 'd' assert d['c'] == 'd' del dd.a del dd[1] # Inner dicts are mapped dd.e = {} dd.e.f = 'g' assert dd['e'].f == 'g' assert d == {'c': 'd', 'e': {'f': 'g'}} 


回答10:

>>> 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' 


回答11:

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



回答12:

I ended up trying BOTH the AttrDict and the Bunch libraries and found them to be way too slow for my uses. After a friend and I looked into it, we found that the main method for writing these libraries results in the library aggressively recursing through a nested object and making copies of the dictionary object throughout. With this in mind, we made two key changes. 1) We made attributes lazy-loaded 2) instead of creating copies of a dictionary object, we create copies of a light-weight proxy object. This is the final implementation. The performance increase of using this code is incredible. When using AttrDict or Bunch, these two libraries alone consumed 1/2 and 1/3 respectively of my request time(what!?). This code reduced that time to almost nothing(somewhere in the range of 0.5ms). This of course depends on your needs, but if you are using this functionality quite a bit in your code, definitely go with something simple like this.

class DictProxy(object):     def __init__(self, obj):         self.obj = obj      def __getitem__(self, key):         return wrap(self.obj[key])      def __getattr__(self, key):         try:             return wrap(getattr(self.obj, key))         except AttributeError:             try:                 return self[key]             except KeyError:                 raise AttributeError(key)      # you probably also want to proxy important list properties along like     # items(), iteritems() and __len__  class ListProxy(object):     def __init__(self, obj):         self.obj = obj      def __getitem__(self, key):         return wrap(self.obj[key])      # you probably also want to proxy important list properties along like     # __iter__ and __len__  def wrap(value):     if isinstance(value, dict):         return DictProxy(value)     if isinstance(value, (tuple, list)):         return ListProxy(value)     return value 

See the original implementation here by https://stackoverflow.com/users/704327/michael-merickel.

The other thing to note, is that this implementation is pretty simple and doesn't implement all of the methods you might need. You'll need to write those as required on the DictProxy or ListProxy objects.



回答13:

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.



回答14:

Old Q&A, but I get something more to talk. Seems no one talk about recursive dict. This is my code:

#!/usr/bin/env python  class Object( dict ):     def __init__( self, data = None ):         super( Object, self ).__init__()         if data:             self.__update( data, {} )      def __update( self, data, did ):         dataid = id(data)         did[ dataid ] = self          for k in data:             dkid = id(data[k])             if did.has_key(dkid):                 self[k] = did[dkid]             elif isinstance( data[k], Object ):                 self[k] = data[k]             elif isinstance( data[k], dict ):                 obj = Object()                 obj.__update( data[k], did )                 self[k] = obj                 obj = None             else:                 self[k] = data[k]      def __getattr__( self, key ):         return self.get( key, None )      def __setattr__( self, key, value ):         if isinstance(value,dict):             self[key] = Object( value )         else:             self[key] = value      def update( self, *args ):         for obj in args:             for k in obj:                 if isinstance(obj[k],dict):                     self[k] = Object( obj[k] )                 else:                     self[k] = obj[k]         return self      def merge( self, *args ):         for obj in args:             for k in obj:                 if self.has_key(k):                     if isinstance(self[k],list) and isinstance(obj[k],list):                         self[k] += obj[k]                     elif isinstance(self[k],list):                         self[k].append( obj[k] )                     elif isinstance(obj[k],list):                         self[k] = [self[k]] + obj[k]                     elif isinstance(self[k],Object) and isinstance(obj[k],Object):                         self[k].merge( obj[k] )                     elif isinstance(self[k],Object) and isinstance(obj[k],dict):                         self[k].merge( obj[k] )                     else:                         self[k] = [ self[k], obj[k] ]                 else:                     if isinstance(obj[k],dict):                         self[k] = Object( obj[k] )                     else:                         self[k] = obj[k]         return self  def test01():     class UObject( Object ):         pass     obj = Object({1:2})     d = {}     d.update({         "a": 1,         "b": {             "c": 2,             "d": [ 3, 4, 5 ],             "e": [ [6,7], (8,9) ],             "self": d,         },         1: 10,         "1": 11,         "obj": obj,     })     x = UObject(d)       assert x.a == x["a"] == 1     assert x.b.c == x["b"]["c"] == 2     assert x.b.d[0] == 3     assert x.b.d[1] == 4     assert x.b.e[0][0] == 6     assert x.b.e[1][0] == 8     assert x[1] == 10     assert x["1"] == 11     assert x[1] != x["1"]     assert id(x) == id(x.b.self.b.self) == id(x.b.self)     assert x.b.self.a == x.b.self.b.self.a == 1      x.x = 12     assert x.x == x["x"] == 12     x.y = {"a":13,"b":[14,15]}     assert x.y.a == 13     assert x.y.b[0] == 14  def test02():     x = Object({         "a": {             "b": 1,             "c": [ 2, 3 ]         },         1: 6,         2: [ 8, 9 ],         3: 11,     })     y = Object({         "a": {             "b": 4,             "c": [ 5 ]         },         1: 7,         2: 10,         3: [ 12 , 13 ],     })     z = {         3: 14,         2: 15,         "a": {             "b": 16,             "c": 17,         }     }     x.merge( y, z )     assert 2 in x.a.c     assert 3 in x.a.c     assert 5 in x.a.c     assert 1 in x.a.b     assert 4 in x.a.b     assert 8 in x[2]     assert 9 in x[2]     assert 10 in x[2]     assert 11 in x[3]     assert 12 in x[3]     assert 13 in x[3]     assert 14 in x[3]     assert 15 in x[2]     assert 16 in x.a.b     assert 17 in x.a.c  if __name__ == '__main__':     test01()     test02() 


回答15:

You can leverage the json module of the standard library with a custom object hook:

import json  class obj(object):     def __init__(self, dict_):         self.__dict__.update(dict_)  def dict2obj(d):     return json.loads(json.dumps(d), object_hook=obj) 

Example usage:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ['hi', {'foo': 'bar'}]} >>> o = dict2obj(d) >>> o.a 1 >>> o.b.c 2 >>> o.d[0] u'hi' >>> o.d[1].foo u'bar' 

And it is not strictly read-only as it is with namedtuple

>>> o.b.c = 3 >>> o.b.c 3 


回答16:

I stumbled upon the case I needed to recursively convert a list of dicts to list of objects, so based on Roberto's snippet here what did the work for me:

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)     elif isinstance(d, (list, tuple,)):         l = []         for item in d:             l.append(dict2obj(item))         return l     else:         return d 

Note that any tuple will be converted to its list equivalent, for obvious reasons.

Hope this helps someone as much as all your answers did for me, guys.



回答17:

Wanted to upload my version of this little paradigm.

class Struct(dict):   def __init__(self,data):     for key, value in data.items():       if isinstance(value, dict):         setattr(self, key, Struct(value))       else:            setattr(self, key, type(value).__init__(value))        dict.__init__(self,data) 

It preserves the attributes for the type that's imported into the class. My only concern would be overwriting methods from within the dictionary your parsing. But otherwise seems solid!



回答18:

from mock import Mock d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]} my_data = Mock(**d)  # We got # my_data.a == 1 


回答19:

What about just assigning your dict to the __dict__ of an empty object?

class Object:     """If your dict is "flat", this is a simple way to create an object from a dict      >>> obj = Object()     >>> obj.__dict__ = d     >>> d.a     1     """     pass 

Of course this fails on your nested dict example unless you walk the dict recursively:

# For a nested dict, you need to recursively update __dict__ def dict2obj(d):     """Convert a dict to an object      >>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}     >>> obj = dict2obj(d)     >>> obj.b.c     2     >>> obj.d     ["hi", {'foo': "bar"}]     """     try:         d = dict(d)     except (TypeError, ValueError):         return d     obj = Object()     for k, v in d.iteritems():         obj.__dict__[k] = dict2obj(v)     return obj 

And your example list element was probably meant to be a Mapping, a list of (key, value) pairs like this:

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


回答20:

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 


回答21:

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" 


回答22:

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.



回答23:

How about this:

from functools import partial d2o=partial(type, "d2o", ()) 

This can then be used like this:

>>> o=d2o({"a" : 5, "b" : 3}) >>> print o.a 5 >>> print o.b 3 


回答24:

I think a dict consists of number, string and dict is enough most time. So I ignore the situation that tuples, lists and other types not appearing in the final dimension of a dict.

Considering inheritance, combined with recursion, it solves the print problem conveniently and also provides two ways to query a data,one way to edit a data.

See the example below, a dict that describes some information about students:

group=["class1","class2","class3","class4",] rank=["rank1","rank2","rank3","rank4","rank5",] data=["name","sex","height","weight","score"]  #build a dict based on the lists above student_dic=dict([(g,dict([(r,dict([(d,'') for d in data])) for r in rank ]))for g in group])  #this is the solution class dic2class(dict):     def __init__(self, dic):         for key,val in dic.items():             self.__dict__[key]=self[key]=dic2class(val) if isinstance(val,dict) else val   student_class=dic2class(student_dic)  #one way to edit: student_class.class1.rank1['sex']='male' student_class.class1.rank1['name']='Nan Xiang'  #two ways to query: print student_class.class1.rank1 print student_class.class1['rank1'] print '-'*50 for rank in student_class.class1:     print getattr(student_class.class1,rank) 

Results:

{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''} {'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''} -------------------------------------------------- {'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''} {'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''} {'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''} {'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''} {'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''} 


回答25:

This also works well too

class DObj(object):     pass  dobj = Dobj() dobj.__dict__ = {'a': 'aaa', 'b': 'bbb'}  print dobj.a >>> aaa print dobj.b >>> bbb 


回答26:

class Struct(dict):     def __getattr__(self, name):         try:             return self[name]         except KeyError:             raise AttributeError(name)      def __setattr__(self, name, value):         self[name] = value      def copy(self):         return Struct(dict.copy(self)) 

Usage:

points = Struct(x=1, y=2) # Changing points['x'] = 2 points.y = 1 # Accessing points['x'], points.x, points.get('x') # 2 2 2 points['y'], points.y, points.get('y') # 1 1 1 # Accessing inexistent keys/attrs  points['z'] # KeyError: z points.z # AttributeError: z # Copying points_copy = points.copy() points.x = 2 points_copy.x # 1 


回答27:

Building off my answer to "python: How to add property to a class dynamically?":

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 functionality.
  • 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.


回答28:

I had some problems with __getattr__ not being called so I constructed a new style class version:

class Struct(object):     '''The recursive class for building and representing objects with.'''     class NoneStruct(object):         def __getattribute__(*args):             return Struct.NoneStruct()          def __eq__(self, obj):             return obj == None      def __init__(self, obj):         for k, v in obj.iteritems():             if isinstance(v, dict):                 setattr(self, k, Struct(v))             else:                 setattr(self, k, v)      def __getattribute__(*args):         try:             return object.__getattribute__(*args)         except:                         return Struct.NoneStruct()      def __repr__(self):         return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for  (k, v) in self.__dict__.iteritems())) 

This version also has the addition of NoneStruct that is returned when an attribute is called that is not set. This allows for None testing to see if an attribute is present. Very usefull when the exact dict input is not known (settings etc.).

bla = Struct({'a':{'b':1}}) print(bla.a.b) >> 1 print(bla.a.c == None) >> True 


回答29:

My dictionary is of this format:

addr_bk = {     'person': [         {'name': 'Andrew', 'id': 123, 'email': 'andrew@mailserver.com',          'phone': [{'type': 2, 'number': '633311122'},                    {'type': 0, 'number': '97788665'}]         },         {'name': 'Tom', 'id': 456,          'phone': [{'type': 0, 'number': '91122334'}]},          {'name': 'Jack', 'id': 7788, 'email': 'jack@gmail.com'}     ] } 

As can be seen, I have nested dictionaries and list of dicts. This is because the addr_bk was decoded from protocol buffer data that converted to a python dict using lwpb.codec. There are optional field (e.g. email => where key may be unavailable) and repeated field (e.g. phone => converted to list of dict).

I tried all the above proposed solutions. Some doesn't handle the nested dictionaries well. Others cannot print the object details easily.

Only the solution, dict2obj(dict) by Dawie Strauss, works best.

I have enhanced it a little to handle when the key cannot be found:

# Work the best, with nested dictionaries & lists! :) # Able to print out all items. class dict2obj_new(dict):     def __init__(self, dict_):         super(dict2obj_new, 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_new(it)             elif isinstance(item, dict):                 self[key] = dict2obj_new(item)      def __getattr__(self, key):         # Enhanced to handle key not found.         if self.has_key(key):             return self[key]         else:             return None 

Then, I tested it with:

# Testing... ab = dict2obj_new(addr_bk)  for person in ab.person:   print "Person ID:", person.id   print "  Name:", person.name   # Check if optional field is available before printing.   if person.email:     print "  E-mail address:", person.email    # Check if optional field is available before printing.   if person.phone:     for phone_number in person.phone:       if phone_number.type == codec.enums.PhoneType.MOBILE:         print "  Mobile phone #:",       elif phone_number.type == codec.enums.PhoneType.HOME:         print "  Home phone #:",       else:         print "  Work phone #:",       print phone_number.number 


回答30:

This is another, alternative, way to convert a list of dictionaries to an object:

def dict2object(in_dict):     class Struct(object):         def __init__(self, in_dict):             for key, value in in_dict.items():                 if isinstance(value, (list, tuple)):                     setattr(                         self, key,                         [Struct(sub_dict) if isinstance(sub_dict, dict)                          else sub_dict for sub_dict in value])                 else:                     setattr(                         self, key,                         Struct(value) if isinstance(value, dict)                         else value)     return [Struct(sub_dict) for sub_dict in in_dict] \         if isinstance(in_dict, list) else Struct(in_dict) 


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!