Преобразовать вложенный Python dict в объект? - PullRequest
490 голосов
/ 20 августа 2009

Я ищу элегантный способ получения данных с использованием доступа к атрибутам в файле с некоторыми вложенными документами и списками (т. Е. Синтаксис объекта в стиле javascript).

Например:

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

Я думаю, это невозможно без рекурсии, но что было бы хорошим способом получить стиль объекта для диктов?

Ответы [ 34 ]

3 голосов
/ 15 августа 2012

Хотел загрузить мою версию этой маленькой парадигмы.

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)

Сохраняет атрибуты для типа, который импортируется в класс. Мое единственное беспокойство будет переписывать методы из словаря вашего анализа. Но в остальном кажется твердым!

3 голосов
/ 21 августа 2013
from mock import Mock
d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
my_data = Mock(**d)

# We got
# my_data.a == 1
2 голосов
/ 20 августа 2009

Вот еще одна реализация:

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()))

[Редактировать] Пропущенный бит, касающийся обработки диктов в списках, а не только других. Добавлено исправление.

2 голосов
/ 20 августа 2009

Вот еще один способ реализовать оригинальное предложение SilentGhost:

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
2 голосов
/ 07 октября 2013

Я думаю, что диктат состоит из числа, строки и диктата в большинстве случаев достаточно. Поэтому я игнорирую ситуацию, когда кортежи, списки и другие типы не появляются в конечном измерении слова.

С учетом наследования в сочетании с рекурсией он удобно решает проблему печати, а также предоставляет два способа запроса данных, один способ редактирования данных.

См. Нижеприведенный пример, который описывает некоторую информацию о студентах:

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)

Результаты:

{'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': ''}
2 голосов
/ 04 мая 2013

Как насчет этого:

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

Это может быть использовано следующим образом:

>>> o=d2o({"a" : 5, "b" : 3})
>>> print o.a
5
>>> print o.b
3
1 голос
/ 25 августа 2015

Вот вложенная готовая версия с namedtuple:

from collections import namedtuple

class Struct(object):
    def __new__(cls, data):
        if isinstance(data, dict):
            return namedtuple(
                'Struct', data.iterkeys()
            )(
                *(Struct(val) for val in data.values())
            )
        elif isinstance(data, (tuple, list, set, frozenset)):
            return type(data)(Struct(_) for _ in data)
        else:
            return data

=>

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> s = Struct(d)
>>> s.d
['hi', Struct(foo='bar')]
>>> s.d[0]
'hi'
>>> s.d[1].foo
'bar'
1 голос
/ 26 августа 2009

Создание моего ответа на " python: Как динамически добавить свойство в класс? ":

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

Вы вызываете makedata в словаре, который вы хотите преобразовать, или, возможно, trydata в зависимости от того, что вы ожидаете в качестве ввода, и он выплевывает объект данных.

Примечания:

  • Вы можете добавить elifs к trydata, если вам нужна дополнительная функциональность.
  • Очевидно, что это не сработает, если вы хотите x.a = {} или подобное.
  • Если вы хотите версию только для чтения, используйте данные класса из исходного ответа .
1 голос
/ 19 июня 2012
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))

Использование:

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 голосов
/ 07 ноября 2014

Этот маленький класс никогда не доставляет мне никаких проблем, просто расширьте его и используйте метод copy ():

  import simplejson as json

  class BlindCopy(object):

    def copy(self, json_str):
        dic = json.loads(json_str)
        for k, v in dic.iteritems():
            if hasattr(self, k):
                setattr(self, k, v);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...