Подтвердить что ты не робот

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

Скажем, у меня есть простой класс в 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

Это здорово! Но, теперь я хочу передать его в dict таким же образом

>>> 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 в обоих случаях. Я не думаю, что просто сделать класс итерабельным решит проблему. Я также пытался много googles с таким количеством различных формулировок "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'}

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

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

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

4b9b3361

Ответ 1

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

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

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

class Wharrgarbl(object):

    ...

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

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

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

Использование именованного кортежа - это очень удобный способ добавить множество функций в ваш класс с минимальными усилиями, включая метод _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) для создания словаря. Прежде чем сделать это, задайте себе вопрос, может ли итерация объекта в виде последовательности пар ключ-значение в этом manner-, хотя и удобная для создания dict, - на самом деле может быть удивительным поведением в других контекстах. Например, задайте себе вопрос "каким должно быть поведение list(my_object)...?"

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

Вариант 3: Реализация протокола сопоставления mapping protocol. Это позволяет вести доступ по ключу, приводить к dict без использования __iter__, а также обеспечивает поведение при распаковке ключевых слов.

Протокол сопоставления требует, чтобы вы предоставили (как минимум) два метода вместе: 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)). Таким образом, possible-, а иногда и convenient- заставляют объект работать по-разному при использовании в качестве итерируемого (например, list(m)) по сравнению с приведением к dict (dict(m)).

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

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

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

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

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

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

Как отмечено в комментариях ниже: стоит упомянуть, что при этом способ abc по сути превращает ваш объектный класс в класс dict -like (при условии, что вы используете 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.

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

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

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

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

Подчеркивание: вышеуказанный "класс" Wharrgarbl на самом деле вовсе не новый класс. Это просто синтаксический сахар для создания типизированных объектов 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'

ПРИМЕЧАНИЕ: вариант 6, скорее всего, НЕ тот, который ищут ОП или другие читатели, основываясь на заголовке этого вопроса. Однако эта опция может быть очень удобной для передачи читателям вашего кода (а также средству проверки типов, например, mypy), что ожидается, что такой объект dict будет иметь определенные ключи с определенными типами значений.


* "Картирование" стало стандартным "именем" типа утки dict -like

Ответ 2

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

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

Ответ 3

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

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}

Ответ 4

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

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)

Ответ 5

Как и многие другие, я хотел бы предложить реализовать функцию 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

Ответ 6

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

Я бы выполнил __what_goes_here__ в классе.

as_dict(self:
    d = {...whatever you need...}
    return d