В python, как привести объект класса к dict

89

Скажем, у меня есть простой класс на Python

class Wharrgarbl(object):
    def __init__(self, a, b, c, sum, version='old'):
        self.a = a
        self.b = b
        self.c = c
        self.sum = 6
        self.version = version

    def __int__(self):
        return self.sum + 9000

    def __what_goes_here__(self):
        return {'a': self.a, 'b': self.b, 'c': self.c}

Я могу очень легко преобразовать его в целое число

>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> int(w)
9006

Что здорово! Но теперь я хочу аналогичным образом преобразовать его в диктант

>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> dict(w)
{'a': 'one', 'c': 'three', 'b': 'two'}

Что мне нужно определить, чтобы это работало? Я попытался заменить оба __dict__и dictна __what_goes_here__, но dict(w)в TypeError: Wharrgarbl object is not iterableобоих случаях получил. Я не думаю, что простое создание итерации класса решит проблему. Я также попытался найти в Google столько разных формулировок «python cast object to dict», сколько мог придумать, но не нашел ничего подходящего: {

Также! Обратите внимание, что вызов w.__dict__не будет делать то, что я хочу, потому что он будет содержать w.versionи w.sum. Я хочу настроить приведение dictтаким же образом, как я могу настроить приведение intс помощью def int(self).

Я знаю, что мог бы сделать что-то подобное

>>> w.__what_goes_here__()
{'a': 'one', 'c': 'three', 'b': 'two'}

Но я предполагаю, что существует питонический способ заставить dict(w)работать, поскольку это тот же тип вещей, что и int(w)или str(w). Если нет более питонического способа, это тоже хорошо, просто подумал, что спрошу. Ой! Я думаю, раз уж это важно, это для python 2.7, но супер-бонусные баллы и для старого и вышедшего из строя решения 2.4.

Есть еще один вопрос: перегрузка __dict __ () в классе python, который похож на этот, но может быть достаточно другим, чтобы гарантировать, что это не дубликат. Я считаю, что OP спрашивает, как преобразовать все данные в его объекты класса как словари. Я ищу более индивидуальный подход, поскольку я не хочу, чтобы все было __dict__включено в словарь, возвращаемый dict(). Что-то вроде публичных и частных переменных может быть достаточно, чтобы объяснить, что я ищу. Объекты будут хранить некоторые значения, используемые в вычислениях, и такие, что мне не нужно / не хочу отображаться в результирующих словарях.

ОБНОВЛЕНИЕ: Я решил пойти по asdictпредложенному маршруту, но это был сложный выбор, выбирая то, что я хотел бы ответить на вопрос. И @RickTeachey, и @ jpmc26 предоставили ответ, который я собираюсь использовать, но у первого было больше информации и вариантов, он также получил тот же результат и получил больше голосов, поэтому я согласился. Тем не менее, все поддержали и спасибо за помощь. Я притаился долго и упорно на StackOverflow , и я стараюсь , чтобы мои ноги в воде больше.

склонность
источник
1
ты это уже видел? docs.python.org/3/reference/…
Garrett R
@GarrettR Это полезно, но на самом деле не дает глубокого понимания этой конкретной проблемы.
Zizouz212
Возможный дубликат перегрузки __dict __ () в классе python
Рик поддерживает Монику
@RickTeachey, это определенно похоже на вопрос о перегрузке ..., хотя я ищу более настраиваемое представление dict. Например, объект может хранить вещи, которые я не хочу видеть в dict.
penchant
Ваш вопрос сформулирован неверно, вы не хотите преобразовывать или преобразовывать свой объект в представление dict (так, чтобы вы могли полностью восстановить объект, если преобразовали обратно). Вы хотите создать представление представления dict только для (скажем) общедоступных переменных экземпляра.
smci

Ответы:

124

Есть как минимум пять- шесть способов. Предпочтительный способ зависит от вашего варианта использования.

Вариант 1. Просто добавьте asdict()метод.

Основываясь на описании проблемы, я бы очень внимательно рассмотрел asdictспособ выполнения вещей, предложенный другими ответами. Это потому, что не похоже, что ваш объект действительно является большой коллекцией:

class Wharrgarbl(object):

    ...

    def asdict(self):
        return {'a': self.a, 'b': self.b, 'c': self.c}

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

Вариант 1а: унаследовать свой класс от 'typing.NamedTuple'(или почти эквивалентного 'collections.namedtuple') и использовать предоставленный для вас _asdictметод .

from typing import NamedTuple

class Wharrgarbl(NamedTuple):
    a: str
    b: str
    c: str
    sum: int = 6
    version: str = 'old'

Именованные кортежи очень удобный способ добавить много функциональных возможности для вашего класса с минимальными усилиями, в том числе с _asdictметодом . Однако есть ограничение в том, что, как показано выше, NT будет включать в себя всех членов _asdict.

Если есть члены, которые вы не хотите включать в свой словарь, вам нужно будет изменить _asdictрезультат:

from typing import NamedTuple

class Wharrgarbl(NamedTuple):
    a: str
    b: str
    c: str
    sum: int = 6
    version: str = 'old'

    def _asdict(self):
        d = super()._asdict()
        del d['sum']
        del d['version']
        return d

Еще одно ограничение - NT только для чтения. Это может быть, а может и не быть желательным.

Вариант 2: Реализовать __iter__.

Вот так, например:

def __iter__(self):
    yield 'a', self.a
    yield 'b', self.b
    yield 'c', self.c

Теперь вы можете просто сделать:

dict(my_object)

Это работает, потому что dict()конструктор принимает итерацию (key, value)пар для создания словаря. Перед тем как это сделать, задайте себе вопрос, может ли повторение объекта как серии пар ключ-значение таким способом - хотя и удобным для создания dict- на самом деле быть неожиданным поведением в других контекстах. Например, задайте себе вопрос "каким должно list(my_object)быть поведение ...?"

Кроме того, обратите внимание, что доступ к значениям напрямую с использованием obj["a"]синтаксиса получения элемента не будет работать, и распаковка аргументов ключевого слова не будет работать. Для них вам потребуется реализовать протокол сопоставления.

Вариант 3: Внедрение в протокол отображения . Это позволяет осуществлять доступ по ключу, преобразовывать в dictбез использования __iter__, а также обеспечивает поведение распаковки ( {**my_obj}) и поведение распаковки ключевых слов, если все ключи являются строками ( dict(**my_obj)).

Протокол сопоставления требует, чтобы вы предоставили (как минимум) два метода вместе: keys()и __getitem__.

class MyKwargUnpackable:
    def keys(self):
        return list("abc")
    def __getitem__(self, key):
        return dict(zip("abc", "one two three".split()))[key]

Теперь вы можете делать такие вещи, как:

>>> m=MyKwargUnpackable()
>>> m["a"]
'one'
>>> dict(m)  # cast to dict directly
{'a': 'one', 'b': 'two', 'c': 'three'}
>>> dict(**m)  # unpack as kwargs
{'a': 'one', 'b': 'two', 'c': 'three'}

Как упоминалось выше, если вы используете достаточно новую версию python, вы также можете распаковать объект протокола сопоставления в словарь, подобный такому (и в этом случае не требуется, чтобы ваши ключи были строками):

>>> {**m}
{'a': 'one', 'b': 'two', 'c': 'three'}

Обратите внимание , что протокол отображения имеет приоритет над по __iter__методе при заливке объекта к dictнепосредственно (без использования kwarg распаковки, т.е. dict(m)). Таким образом, возможно - а иногда и удобно - заставить объект вести себя по-разному при использовании в качестве итеративного (например, list(m)) по сравнению с приведением к dict( dict(m)).

ПОДЧЕРКНУТО : просто потому, что вы МОЖЕТЕ использовать протокол сопоставления, НЕ означает, что вы ДОЛЖНЫ это делать . Имеет ли смысл передавать ваш объект как набор пар ключ-значение или как аргументы и значения ключевых слов? Имеет ли смысл доступ к нему с помощью ключа, как и к словарю?

Если ответ на эти вопросы утвердительный , вероятно, стоит рассмотреть следующий вариант.

Вариант 4: изучите возможность использования 'collections.abcмодуля ' .

Наследование вашего класса от 'collections.abc.Mappingили 'collections.abc.MutableMappingсигнализирует другим пользователям о том, что для всех намерений и целей ваш класс является отображением * и можно ожидать, что он будет вести себя таким образом.

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

Этот ответ тоже может быть полезен.

Как отмечено в комментариях ниже: стоит упомянуть, что выполнение этого способа abc по существу превращает ваш объектный класс в dict-подобный класс (при условии, что вы используете, MutableMappingа не Mappingбазовый класс только для чтения ). Все, что вы могли бы сделать dict, вы могли бы сделать с вашим собственным объектом класса. Это может быть, а может быть, и нежелательно.

Также обратите внимание на числовые abcs в numbersмодуле:

https://docs.python.org/3/library/numbers.html

Поскольку вы также преобразуете свой объект в intкласс, возможно, имеет смысл превратить ваш класс в полноценный, intчтобы в приведении не было необходимости.

Вариант 5. Рассмотрите возможность использования dataclassesмодуля (только Python 3.7), который включает удобный asdict()служебный метод.

from dataclasses import dataclass, asdict, field, InitVar

@dataclass
class Wharrgarbl(object):
    a: int
    b: int
    c: int
    sum: InitVar[int]  # note: InitVar will exclude this from the dict
    version: InitVar[str] = "old"

    def __post_init__(self, sum, version):
        self.sum = 6  # this looks like an OP mistake?
        self.version = str(version)

Теперь вы можете это сделать:

    >>> asdict(Wharrgarbl(1,2,3,4,"X"))
    {'a': 1, 'b': 2, 'c': 3}

Вариант 6: использование typing.TypedDict, добавленное в python 3.8 .

ПРИМЕЧАНИЕ: вариант 6, скорее всего, НЕ то, что ищут OP или другие читатели, исходя из названия этого вопроса. См. Дополнительные комментарии ниже.

class Wharrgarbl(TypedDict):
    a: str
    b: str
    c: str

Используя эту опцию, результирующий объект будетdict (курсив: это не a Wharrgarbl). Нет никакой причины «преобразовывать» его в диктант (если только вы не делаете копию).

А поскольку объект является adict , сигнатура инициализации идентична сигнатуре инициализации dictи, как таковая, принимает только аргументы ключевого слова или другой словарь.

    >>> w = Wharrgarbl(a=1,b=2,b=3)
    >>> w
    {'a': 1, 'b': 2, 'c': 3}
    >>> type(w)
    <class 'dict'>

Подчеркнуто : приведенный выше «класс» на Wharrgarblсамом деле вовсе не новый класс. Это просто синтаксический сахар для создания типизированных dictобъектов с полями разных типов для проверки типов.

Таким образом, этот параметр может быть довольно удобным для сигнализации читателям вашего кода (а также средству проверки типов, например mypy), что такой dictобъект, как ожидается, будет иметь определенные ключи с определенными типами значений.

Но это означает, что вы не можете, например, добавить другие методы, хотя можете попробовать:

class MyDict(TypedDict):
    def my_fancy_method(self):
        return "world changing result"

... но это не сработает:

>>> MyDict().my_fancy_method()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'my_fancy_method'

* «Маппинг» стал стандартным «названием» dictутки -подобного типа.

Рик поддерживает Монику
источник
1
Это является способ , чтобы получить эффект ОП хочет, но вы должны , возможно , уточнить , что у него есть другие эффекты, например , for k, v in my_objectтакже будет включен.
zwol
@RickTeachey Я вижу, к чему вы клоните с помощью точки asdict. Мне, конечно, не нужно, чтобы объекты были повторяемыми.
penchant
Почему мы можем просто использовать функцию vars, как упомянуто в другой ссылке. Https://stackoverflow.com/questions/61517/python-dictionary-from-an-objects-fields. Я что-то упускаю в этом вопросе?
пользователь
@user вопрос Ор разыскивается только члены a, bи cв dict. varsФункция возвращает словарь со всеми членами объекта , включая те , которые не были хотели: sumи version. В некоторых случаях varsбудет работать, но, по моему опыту, это не совсем идиоматический питон. Обычно люди более ясно, говоря : «Я сейчас кастинг мой объект в словарь», делая , например obj.asdict(). С vars, обычно это что-то вроде, obj_kwargs = vars(obj)за которым следует MyObject(**obj_kwargs). Другими словами: varsв основном используется для копирования объектов.
Рик поддерживает Монику
как насчет добавления метода, который возвращает self.__dict__?
user32882 03 нояб.2020,
19

Не существует волшебного метода, который сделает то, что вы хотите. Ответ просто назовите его соответствующим образом. asdict- разумный выбор для простого преобразования в dictформат, в первую очередь вдохновленный namedtuple. Однако ваш метод, очевидно, будет содержать особую логику, которая может быть не сразу очевидна из этого имени; вы возвращаете только подмножество состояния класса. Если вы можете придумать немного более подробное название, которое ясно передает концепции, тем лучше.

В других ответах предлагается использовать __iter__, но если ваш объект действительно не повторяется (представляет собой серию элементов), это действительно имеет мало смысла и представляет собой неудобное злоупотребление методом. Тот факт, что вы хотите отфильтровать некоторые состояния класса, делает этот подход еще более сомнительным.

jpmc26
источник
да, я пытаюсь достичь возврата подмножества всего состояния класса, значений релевантности. У меня хранятся некоторые «скрытые» значения, которые ускоряют вычисления / сравнения и т. Д., Но это не то, что человек хотел бы увидеть или поиграть, и они также не представляют сам объект.
penchant
@ tr3buchet В вашем примере кода эти значения передаются инициализатору . Это так в вашем реальном коде? Кажется странным, что кешированные вычисления будут передаваться в качестве аргументов.
jpmc26
совсем нет, я просто делал это для примера
penchant
Просто хочу отметить, что документы, похоже, оправдывают использование, __iter__как предлагали другие ответы: «Для сопоставлений он должен перебирать ключи контейнера, а также должен быть доступен как метод iterkeys ()».
dbenton
@dbenton Это не то, что делает OP или что рекомендуют другие ответы. Объект OP не является «контейнером», который в этом контексте в основном используется как синоним коллекции. Объект OP также не является тем, что обычно подразумевается под отображением. Отображение - это то, что имеет произвольное количество ключей и значений и должно расширять Mappingбазовый класс. Объект, имеющий определенные именованные атрибуты, не попадает в эту категорию.
jpmc26
11

что-то вроде этого, вероятно, сработает

class MyClass:
    def __init__(self,x,y,z):
       self.x = x
       self.y = y
       self.z = z
    def __iter__(self): #overridding this to return tuples of (key,value)
       return iter([('x',self.x),('y',self.y),('z',self.z)])

dict(MyClass(5,6,7)) # because dict knows how to deal with tuples of (key,value)
Джоран Бизли
источник
ох интересно! итерация кортежей, я не учел этого. Сделаю быстрый тест
penchant
Кроме того, при возврате элемента списка кортежей все значения x, y и z переносятся в список, на котором основан этот элемент. В случае, если 3 оператора yield, возможно (крошечное окно, но все же есть), что запуск этого в многопоточной среде может привести к тому, что другой поток изменит атрибут y или атрибут z, так что сообщенные x, y и z несовместимы.
PaulMcG
@PaulMcGuire: гарантирует ли CPython, что все они будут согласованы даже с кодом Джорана? Я бы подумал, что можно запланировать другой поток, например, после создания первого кортежа с x, но до второго кортежа с y. Я ошибаюсь, и на самом деле понимание списка атомарно по отношению к планированию потоков?
Стив Джессоп
Теперь, когда вы спрашиваете, я понимаю, что даже понимание списка не гарантировано атомарно, поскольку переключатель потоков Python делает это на уровне байт-кода, а не на уровне исходного кода. Но ваше воздействие распространяется только на время создания списка - после того, как список был создан, любые обновления свойств не будут иметь значения (если значения свойств не являются изменяемыми объектами). Лучше всего использовать disмодуль для просмотра фактических байт-кодов.
PaulMcG
11

Я думаю, это сработает для вас.

class A(object):
    def __init__(self, a, b, c, sum, version='old'):
        self.a = a
        self.b = b
        self.c = c
        self.sum = 6
        self.version = version

    def __int__(self):
        return self.sum + 9000

    def __iter__(self):
        return self.__dict__.iteritems()

a = A(1,2,3,4,5)
print dict(a)

Вывод

{'a': 1, 'c': 3, 'b': 2, 'sum': 6, 'version': 5}

Гарретт Р
источник
что случилось с суммой и версией? Чем это отличается от простого доступа, __dict__как предлагается в другом ответе?
Джоран Бизли
это не диктат. вызов iteritems возвращает итератор
Garrett R
1
Мне нравится этот ответ ... но ему нужно немного поработать, прежде чем он будет соответствовать требованиям OP ...
Джоран Бисли
этот ответ действительно ничем не отличается от @JoranBeasley. Разница в том, что это выборочно, тогда как вы захватываете все, self.__dict__что содержит элементы, которые я не хочу иметь в словарной версии объекта.
penchant
6

Как и многие другие, я бы предложил реализовать функцию to_dict () вместо (или в дополнение) к разрешению преобразования в словарь. Я думаю, это делает более очевидным то, что класс поддерживает такую ​​функциональность. Вы можете легко реализовать такой метод следующим образом:

def to_dict(self):
    class_vars = vars(MyClass)  # get any "default" attrs defined at the class level
    inst_vars = vars(self)  # get any attrs defined on the instance (self)
    all_vars = dict(class_vars)
    all_vars.update(inst_vars)
    # filter out private attributes
    public_vars = {k: v for k, v in all_vars.items() if not k.startswith('_')}
    return public_vars
tvt173
источник
1

Трудно сказать, не зная всего контекста проблемы, но я бы не стал отменять __iter__.

Я бы реализовал __what_goes_here__на своем классе.

as_dict(self:
    d = {...whatever you need...}
    return d
шумовая вода
источник
Нет, это то, в чем хранятся переменные.
Zizouz212
3
Я специально указал, что w.__dict__это не то, что мне нужно.
penchant
Но __dict__ведь это можно было изменить, не так ли?
noisewaterphd
@noisewaterphd нет, это волшебный метод (возможно, вы могли бы его переопределить, но последствия этого, мягко говоря, пугающие)
Джоран Бисли
1
Почему бы не наследовать от dict или, что еще лучше, просто создать метод в классе, чтобы вернуть его как dict.
noisewaterphd
0

Я пытаюсь написать класс, который одновременно является а listили а dict. Я хочу, чтобы программист имел возможность «преобразовать» этот объект в list(опускание клавиш) или dict(с помощью клавиш).

Посмотрите, как Python в настоящее время выполняет dict()приведение: он вызывает Mapping.update()переданный объект. Это код из репозитория Python :

def update(self, other=(), /, **kwds):
    ''' D.update([E, ]**F) -> None.  Update D from mapping/iterable E and F.
        If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
        If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
        In either case, this is followed by: for k, v in F.items(): D[k] = v
    '''
    if isinstance(other, Mapping):
        for key in other:
            self[key] = other[key]
    elif hasattr(other, "keys"):
        for key in other.keys():
            self[key] = other[key]
    else:
        for key, value in other:
            self[key] = value
    for key, value in kwds.items():
        self[key] = value

otherБольшинство людей имеет в виду последний частичный случай оператора if, в котором он повторяется . Однако, как видите, также возможно keys()владение недвижимостью. Это в сочетании с a __getitem__()должно упростить правильное преобразование подкласса в словарь:

class Wharrgarbl(object):
    def __init__(self, a, b, c, sum, version='old'):
        self.a = a
        self.b = b
        self.c = c
        self.sum = 6
        self.version = version

    def __int__(self):
        return self.sum + 9000

    def __keys__(self):
        return ["a", "b", "c"]

    def __getitem__(self, key):
        # have obj["a"] -> obj.a
        return self.__getattribute__(key)

Тогда это сработает:

>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> dict(w)
{'a': 'one', 'c': 'three', 'b': 'two'}
Жереми
источник
Вы реализовали протокол сопоставления. См. Вариант 3 в моем ответе выше. Если вы также хотите listотбросить ключи, вы можете добавить __iter__метод, который выполняет итерацию по значениям, и приведение к по- dictпрежнему будет работать. Обратите внимание, что обычно со словарями, если вы приводите к списку, он возвращает KEYS, а не значения, которые вам требуются. Это может быть неожиданным для других людей, использующих ваш код, если только не очевидно, почему это могло произойти.
Рик поддерживает Монику