Convert nested Python dict to object?

后端 未结 30 1929
时光取名叫无心
时光取名叫无心 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:01

    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"
    
    0 讨论(0)
  • 2020-11-22 10:02
    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
    
    0 讨论(0)
  • 2020-11-22 10:03
    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'
    
    0 讨论(0)
  • 2020-11-22 10:03

    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, i.e. you can change values – not structure:

    >>> o.b.c = 3
    >>> o.b.c
    3
    
    0 讨论(0)
  • 2020-11-22 10:03

    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.

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

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