Python словарь из полей объекта

344

Знаете ли вы, есть ли встроенная функция для создания словаря из произвольного объекта? Я хотел бы сделать что-то вроде этого:

>>> class Foo:
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> props(f)
{ 'bar' : 'hello', 'baz' : 'world' }

ПРИМЕЧАНИЕ: не должно включать методы. Только поля.

Хулио Сезар
источник

Ответы:

423

Обратите внимание, что в Python 2.7 рекомендуется использовать классы нового стиля (не требуется в Python 3), т.е.

class Foo(object):
   ...

Кроме того, есть разница между «объектом» и «классом». Чтобы построить словарь из произвольного объекта , достаточно использовать __dict__. Обычно вы объявляете свои методы на уровне класса и свои атрибуты на уровне экземпляра, поэтому все __dict__должно быть в порядке. Например:

>>> class A(object):
...   def __init__(self):
...     self.b = 1
...     self.c = 2
...   def do_nothing(self):
...     pass
...
>>> a = A()
>>> a.__dict__
{'c': 2, 'b': 1}

Лучшим подходом (предложенным Робертом в комментариях) является встроенная varsфункция:

>>> vars(a)
{'c': 2, 'b': 1}

Кроме того, в зависимости от того, что вы хотите сделать, было бы неплохо унаследовать от dict. Тогда ваш класс уже словарь, и если вы хотите, вы можете переопределить getattrи / или setattrпозвонить и установить dict. Например:

class Foo(dict):
    def __init__(self):
        pass
    def __getattr__(self, attr):
        return self[attr]

    # etc...
user6868
источник
3
Что произойдет, если у одного из атрибутов А будет собственный геттер? (функция с декоратором @property)? Это все еще появляется в ____dict____? Какова будет его стоимость?
Закданс
11
__dict__не будет работать, если объект использует слоты (или определен в модуле C).
Сурьма
1
Есть ли эквивалент этого метода для объектов класса? IE Вместо того, чтобы использовать f = Foo () и затем делать f .__ dict__, делайте напрямую Foo .__ dict__?
Чиффа
50
Извините, я прихожу к этому поздно, но не должен vars(a)ли это делать? Для меня предпочтительнее вызывать __dict__напрямую.
Роберт
2
для второго примера было бы лучше сделать __getattr__ = dict.__getitem__точную копию поведения, тогда вы также захотите __setattr__ = dict.__setitem__и __delattr__ = dict.__delitem__для полноты.
Tadhg McDonald-Jensen
142

Вместо x.__dict__этого на самом деле это более питонический vars(x).

Берислав Лопач
источник
3
Согласовано. Обратите внимание, что вы также можете преобразовать другой путь (dict-> class), набрав MyClass(**my_dict), предполагая, что вы определили конструктор с параметрами, которые отражают атрибуты класса. Нет необходимости обращаться к закрытым атрибутам или переопределять dict.
tvt173
2
Можете ли вы объяснить, почему это больше Pythonic?
Хью W
1
Во-первых, Python, как правило, избегает вызова дурных элементов напрямую, и почти всегда есть метод или функция (или оператор) для косвенного доступа к ним. В общем, атрибуты и методы dunder являются деталями реализации, и использование функции «обертка» позволяет разделить их. Во-вторых, таким образом вы можете переопределить varsфункцию и ввести дополнительные функциональные возможности без изменения самого объекта.
Берислав Лопач
1
Это все еще терпит неудачу, если Ваш класс использует __slots__все же.
cz
Это правильно, и я всегда чувствовал, что это было бы хорошим направлением для расширения vars, то есть для возврата эквивалента __dict__для «прорезанных» классов. На данный момент его можно эмулировать, добавив __dict__возвращаемое свойство {x: getattr(self, x) for x in self.__slots__}(хотя и не уверен, что это как-то влияет на производительность или поведение).
Берислав Лопач
59

dirВстроенный дадут вам все атрибуты объекта, включая специальные методы , такие как __str__, __dict__и целый букет других , которые вы , вероятно , не хотите. Но вы можете сделать что-то вроде:

>>> class Foo(object):
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> [name for name in dir(f) if not name.startswith('__')]
[ 'bar', 'baz' ]
>>> dict((name, getattr(f, name)) for name in dir(f) if not name.startswith('__')) 
{ 'bar': 'hello', 'baz': 'world' }

Таким образом, можно расширить это, чтобы возвращать только атрибуты данных, а не методы, определяя props функцию следующим образом:

import inspect

def props(obj):
    pr = {}
    for name in dir(obj):
        value = getattr(obj, name)
        if not name.startswith('__') and not inspect.ismethod(value):
            pr[name] = value
    return pr
дР.
источник
1
Этот код включает методы. Есть ли способ исключить методы? Мне нужны только поля объекта. Спасибо
Хулио Сезар
ismethodне ловит функции. Пример: inspect.ismethod(str.upper). inspect.isfunctionне намного полезнее, хотя. Не уверен, как подойти к этому сразу.
Этеш Чоудхури
Я сделал несколько настроек, чтобы грубо повторяться и игнорировать все ошибки здесь, спасибо! gist.github.com/thorsummoner/bf0142fd24974a0ced778768a33a3069
ThorSummoner
26

Я согласился с комбинацией обоих ответов:

dict((key, value) for key, value in f.__dict__.iteritems() 
    if not callable(value) and not key.startswith('__'))
Хулио Сезар
источник
Это также работает, но имейте в виду, что оно даст вам только атрибуты, установленные для экземпляра, а не для класса (как, например, класс Foo в вашем примере) ...
dF.
Итак, jcarrascal, вам лучше обернуть вышеупомянутый код в функцию, подобную props (), тогда вы можете вызывать либо props (f), либо props (Foo). Обратите внимание, что вам почти всегда лучше писать функцию, чем писать «встроенный» код.
Quamrana
Замечательно, заметьте, это для python2.7, для python3 relpace iteritems () с просто items ().
Мортен
А как насчет staticmethod? Это не так callable.
Алекс
17

Я подумал, что найду время, чтобы показать вам, как вы можете перевести объект на диктовку dict(obj).

class A(object):
    d = '4'
    e = '5'
    f = '6'

    def __init__(self):
        self.a = '1'
        self.b = '2'
        self.c = '3'

    def __iter__(self):
        # first start by grabbing the Class items
        iters = dict((x,y) for x,y in A.__dict__.items() if x[:2] != '__')

        # then update the class items with the instance items
        iters.update(self.__dict__)

        # now 'yield' through the items
        for x,y in iters.items():
            yield x,y

a = A()
print(dict(a)) 
# prints "{'a': '1', 'c': '3', 'b': '2', 'e': '5', 'd': '4', 'f': '6'}"

Ключевым разделом этого кода является __iter__функция.

Как поясняется в комментариях, первое, что мы делаем, это возьмем элементы класса и предотвратим все, что начинается с «__».

Как только вы это создали dict, вы можете использовать updateфункцию dict и передать экземпляр __dict__.

Это даст вам полный класс + экземпляр словаря членов. Теперь все, что осталось, - это перебрать их и получить результаты.

Кроме того, если вы планируете использовать это много, вы можете создать @iterableдекоратор класса.

def iterable(cls):
    def iterfn(self):
        iters = dict((x,y) for x,y in cls.__dict__.items() if x[:2] != '__')
        iters.update(self.__dict__)

        for x,y in iters.items():
            yield x,y

    cls.__iter__ = iterfn
    return cls

@iterable
class B(object):
    d = 'd'
    e = 'e'
    f = 'f'

    def __init__(self):
        self.a = 'a'
        self.b = 'b'
        self.c = 'c'

b = B()
print(dict(b))
Seaux
источник
Это также захватит все методы, но нам нужны только поля класса + экземпляра. Может dict((x, y) for x, y in KpiRow.__dict__.items() if x[:2] != '__' and not callable(y))это решит? Но все еще могут быть staticметоды :(
Алекс
15

Чтобы построить словарь из произвольного объекта , достаточно использовать __dict__.

Это пропускает атрибуты, которые объект наследует от своего класса. Например,

class c(object):
    x = 3
a = c()

hasattr (a, 'x') имеет значение true, но «x» не появляется в .__ dict__

вдавливание
источник
В этом случае какое решение? Так vars()как не работает
must_be_working
@should_be_working dir- решение в этом случае. Смотрите другой ответ об этом.
Альберт
8

Поздний ответ, но с учетом полноты и пользы гуглеров:

def props(x):
    return dict((key, getattr(x, key)) for key in dir(x) if key not in dir(x.__class__))

Здесь не будут отображаться методы, определенные в классе, но все равно будут отображаться поля, в том числе те, которые назначены лямбдам, или те, которые начинаются с двойного подчеркивания.

Score_Under
источник
6

Я думаю, что самый простой способ - создать атрибут getitem для класса. Если вам нужно записать объект, вы можете создать собственный setattr . Вот пример для getitem :

class A(object):
    def __init__(self):
        self.b = 1
        self.c = 2
    def __getitem__(self, item):
        return self.__dict__[item]

# Usage: 
a = A()
a.__getitem__('b')  # Outputs 1
a.__dict__  # Outputs {'c': 2, 'b': 1}
vars(a)  # Outputs {'c': 2, 'b': 1}

dict генерирует атрибуты объектов в словарь, и объект словаря можно использовать для получения необходимого вам элемента.

radtek
источник
После этого ответа до сих пор не ясно, как получить словарь из объекта. Не свойства, а весь словарь;)
maxkoryukov
6

Недостаток использования __dict__ является то, что он мелкий; он не будет преобразовывать подклассы в словари.

Если вы используете Python3.5 или выше, вы можете использовать jsons:

>>> import jsons
>>> jsons.dump(f)
{'bar': 'hello', 'baz': 'world'}
RH
источник
3

Если вы хотите перечислить часть своих атрибутов, переопределите __dict__:

def __dict__(self):
    d = {
    'attr_1' : self.attr_1,
    ...
    }
    return d

# Call __dict__
d = instance.__dict__()

Это очень помогает, если вы instanceполучаете большие данные блока и хотите отправить dв Redis очередь сообщений.

coanor
источник
__dict__является атрибутом, а не методом, поэтому этот пример изменяет интерфейс (т. е. его нужно вызывать как вызываемый), поэтому он не переопределяет его.
Берислав Лопач
0

ПИТОН 3:

class DateTimeDecoder(json.JSONDecoder):

   def __init__(self, *args, **kargs):
        JSONDecoder.__init__(self, object_hook=self.dict_to_object,
                         *args, **kargs)

   def dict_to_object(self, d):
       if '__type__' not in d:
          return d

       type = d.pop('__type__')
       try:
          dateobj = datetime(**d)
          return dateobj
       except:
          d['__type__'] = type
          return d

def json_default_format(value):
    try:
        if isinstance(value, datetime):
            return {
                '__type__': 'datetime',
                'year': value.year,
                'month': value.month,
                'day': value.day,
                'hour': value.hour,
                'minute': value.minute,
                'second': value.second,
                'microsecond': value.microsecond,
            }
        if isinstance(value, decimal.Decimal):
            return float(value)
        if isinstance(value, Enum):
            return value.name
        else:
            return vars(value)
    except Exception as e:
        raise ValueError

Теперь вы можете использовать приведенный выше код внутри вашего собственного класса:

class Foo():
  def toJSON(self):
        return json.loads(
            json.dumps(self, sort_keys=True, indent=4, separators=(',', ': '), default=json_default_format), cls=DateTimeDecoder)


Foo().toJSON() 
spattanaik75
источник
0

vars() отлично, но не работает для вложенных объектов объектов

Преобразовать вложенный объект из объектов в dict:

def to_dict(self):
    return json.loads(json.dumps(self, default=lambda o: o.__dict__))
Рики Леви
источник