Convert nested Python dict to object?

后端 未结 30 1931
时光取名叫无心
时光取名叫无心 2020-11-22 09:28

I\'m searching for an elegant way to get data using attribute access on a dict with some nested dicts and lists (i.e. javascript-style object syntax).

For example:

相关标签:
30条回答
  • 2020-11-22 10:04

    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
    
    0 讨论(0)
  • 2020-11-22 10:07

    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

    0 讨论(0)
  • 2020-11-22 10:07
    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.

    0 讨论(0)
  • 2020-11-22 10:08

    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.

    0 讨论(0)
  • 2020-11-22 10:10

    The simplest way would be using collections.namedtuple.

    I find the following 4-liner the most beautiful, which supports nested dictionaries:

    def dict_to_namedtuple(typename, data):
        return namedtuple(typename, data.keys())(
            *(dict_to_namedtuple(typename + '_' + k, v) if isinstance(v, dict) else v for k, v in data.items())
        )
    

    The output will look good as well:

    >>> nt = dict_to_namedtuple('config', {
    ...     'path': '/app',
    ...     'debug': {'level': 'error', 'stream': 'stdout'}
    ... })
    
    >>> print(nt)
    config(path='/app', debug=config_debug(level='error', stream='stdout'))
    
    >>> print(nt.debug.level)
    'error'
    
    0 讨论(0)
  • 2020-11-22 10:11

    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
    
    0 讨论(0)
提交回复
热议问题