Скажем, у меня есть простой класс на 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. Просто добавьте
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
(курсив: это не aWharrgarbl
). Нет никакой причины «преобразовывать» его в диктант (если только вы не делаете копию).А поскольку объект является a
dict
, сигнатура инициализации идентична сигнатуре инициализации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
утки -подобного типа.источник
for k, v in my_object
также будет включен.a
,b
иc
вdict
.vars
Функция возвращает словарь со всеми членами объекта , включая те , которые не были хотели:sum
иversion
. В некоторых случаяхvars
будет работать, но, по моему опыту, это не совсем идиоматический питон. Обычно люди более ясно, говоря : «Я сейчас кастинг мой объект в словарь», делая , напримерobj.asdict()
. Сvars
, обычно это что-то вроде,obj_kwargs = vars(obj)
за которым следуетMyObject(**obj_kwargs)
. Другими словами:vars
в основном используется для копирования объектов.self.__dict__
?Не существует волшебного метода, который сделает то, что вы хотите. Ответ просто назовите его соответствующим образом.
asdict
- разумный выбор для простого преобразования вdict
формат, в первую очередь вдохновленныйnamedtuple
. Однако ваш метод, очевидно, будет содержать особую логику, которая может быть не сразу очевидна из этого имени; вы возвращаете только подмножество состояния класса. Если вы можете придумать немного более подробное название, которое ясно передает концепции, тем лучше.В других ответах предлагается использовать
__iter__
, но если ваш объект действительно не повторяется (представляет собой серию элементов), это действительно имеет мало смысла и представляет собой неудобное злоупотребление методом. Тот факт, что вы хотите отфильтровать некоторые состояния класса, делает этот подход еще более сомнительным.источник
__iter__
как предлагали другие ответы: «Для сопоставлений он должен перебирать ключи контейнера, а также должен быть доступен как метод iterkeys ()».Mapping
базовый класс. Объект, имеющий определенные именованные атрибуты, не попадает в эту категорию.что-то вроде этого, вероятно, сработает
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)
источник
x
, но до второго кортежа сy
. Я ошибаюсь, и на самом деле понимание списка атомарно по отношению к планированию потоков?dis
модуль для просмотра фактических байт-кодов.Я думаю, это сработает для вас.
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__
как предлагается в другом ответе?self.__dict__
что содержит элементы, которые я не хочу иметь в словарной версии объекта.Как и многие другие, я бы предложил реализовать функцию 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
источник
Трудно сказать, не зная всего контекста проблемы, но я бы не стал отменять
__iter__
.Я бы реализовал
__what_goes_here__
на своем классе.as_dict(self: d = {...whatever you need...} return d
источник
w.__dict__
это не то, что мне нужно.__dict__
ведь это можно было изменить, не так ли?Я пытаюсь написать класс, который одновременно является а
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'}
источник
list
отбросить ключи, вы можете добавить__iter__
метод, который выполняет итерацию по значениям, и приведение к по-dict
прежнему будет работать. Обратите внимание, что обычно со словарями, если вы приводите к списку, он возвращает KEYS, а не значения, которые вам требуются. Это может быть неожиданным для других людей, использующих ваш код, если только не очевидно, почему это могло произойти.