У python, як мені передати об’єкт класу на dict


85

Скажімо, у мене є простий клас на 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. Я не думаю, що якщо зробити клас ітеративним, це вирішить проблему. Я також спробував багато 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'}

Але я припускаю, що існує пітонічний спосіб зробити 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,


@RickTeachey це, безумовно, схоже на питання "Перевантаження ...", хоча я шукаю більш настроюване представлення дикту. Наприклад, об'єкт може зберігати речі, які я не хочу бачити в дикті.
схильність

Ваше запитання вводиться в оману, ви не хочете перетворювати або перетворювати свій об'єкт на диктоване представлення (так, щоб ви могли повністю відновити об'єкт, якщо перетворили назад). Вам потрібно лише створити подання представлення dict для (скажімо) відкритих змінних екземпляра.
smci

Відповіді:


117

Є принаймні п’ять шість способів. Кращий спосіб залежить від того, який ви використовуєте.

Варіант 1: Просто додайте asdict()метод.

На основі опису проблеми я б дуже розглянув asdictспосіб здійснення речей, запропонований іншими відповідями. Це тому, що, здається, ваш об’єкт насправді не є колекцією:

class Wharrgarbl(object):

    ...

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

Використання інших варіантів, наведених нижче, може заплутати інших, за винятком випадків, коли дуже очевидно, які саме члени об'єкта будуть, а не будуть повторюватися або вказуватися як пари ключ-значення.

Варіант 1a: успадкуйте свій клас від '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методом . Однак обмеження полягає в тому, що, як показано вище, НЗ включатиме у себе всіх членів _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"]синтаксису елемента get не працюватиме, а розпакування аргументів ключового слова не буде працювати. Для них вам потрібно буде застосувати протокол відображення.

Варіант 3: Впровадження в протокол відображення . Це дозволяє поведінку доступу за ключем, перекидання на a 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у більшість випадків буде просто додатковим непотрібним кроком.

Ця відповідь також може бути корисною.

Як зазначалося в коментарях нижче: варто згадати, що, роблячи це, метод 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, швидше за все, НЕ те, що шукає ОР чи інші читачі, засновані на назві цього запитання. Дивіться додаткові коментарі нижче.

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

Використовуючи цей параметр, результуючим об’єктом є adict (наголос: це не 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 Я бачу, на що ти рухаєшся з пунктом судження. Мені, звичайно, не потрібні об'єкти, щоб бути ітерабельними.
схильність

Чому ми можемо просто використовувати функцію vars, як згадувалося в інших посиланнях /programming/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

17

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

Інші відповіді пропонують використовувати __iter__, але якщо ваш об’єкт не є справді ітерабельним (представляє ряд елементів), це насправді мало сенсу і являє собою незручне зловживання методом. Той факт, що ви хочете відфільтрувати деякий стан класу, робить цей підхід ще більш сумнівним.


так, повернення підмножини всього стану класу, цінностей релевантності - це те, чого я намагаюся досягти. У мене є кілька «прихованих» значень, які пришвидшують процес обчислень / порівнянь тощо, але це ніщо, що людина хотіла б бачити чи грати, і вони також насправді не представляють сам об’єкт.
схильність

@ tr3buchet У вашому прикладі коду ці значення передаються в ініціалізатор . Це так у вашому реальному коді? Дивно, що кешовані обчислення передаються як аргументи.
jpmc26,

зовсім не, я просто робив це, щоб подати приклад
схильність

Тільки хочу зазначити, що документи, здається, потурають використанню, __iter__як пропонують інші відповіді: "Для зіставлення воно має перебирати ключі контейнера, а також повинно бути доступним як метод iterkeys ()."
dbenton

@dbenton Це не те, що робить ОП, або те, що рекомендують інші відповіді. Об'єкт 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)

ой цікаво! ітерабель кортежів, я не вважав це. Я зроблю швидкий тест
схильність

Крім того, повернення ітера списку кортежів робить знімки всіх значень x, y та z до списку, на якому ґрунтується ітер. У випадку, якщо 3 оператори дають, можливо (крихітне вікно, але тим не менше), що запуск цього в потоковому середовищі може призвести до того, що інший потік модифікує атрибут y або атрибут z таким, що повідомляється x, y та z не узгоджуються.
PaulMcG

@PaulMcGuire: чи гарантує CPython, що всі вони будуть узгоджені навіть з кодом Джорана? Я міг би подумати, що можна запланувати інший потік, наприклад, після створення першого кортежа з x, але до другого кортежу з y. Я помиляюся, і насправді розуміння списку є атомним щодо планування потоків?
Steve Jessop

Тепер, коли ви запитуєте, я усвідомлюю, що навіть розуміння списку не гарантоване атомарне, оскільки перемикач потоків 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
Мені подобається ця відповідь ... але їй потрібно трохи більше попрацювати, перш ніж вона відповідатиме вимогам ОП ...
Джоран Бізлі

ця відповідь насправді нічим не відрізняється від @JoranBeasley. Різниця полягає в тому, що його вибірковість, тоді як ви захоплюєте ціле, self.__dict__що містить елементи, які я не хочу мати у словниковій версії об’єкта.
схильність

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

1

Важко сказати, не знаючи цілого контексту проблеми, але я б не перевизначив __iter__.

Я б реалізував __what_goes_here__на класі.

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

Ні, в цьому зберігаються змінні.
Zizouz212,

3
Я спеціально зазначив, що w.__dict__не роблю того, що мені потрібно.
схильність

Але це __dict__можна було б замінити, чи не так?
noisewaterphd

@noisewaterphd ні, це магічний метод (можливо, ти міг би його перевизначити, але наслідки це, м'яко кажучи, страшні)
Джоран Бізлі,

1
Чому б не успадкувати від dict, або, можливо, ще краще, просто створити метод для класу, щоб повернути його як dict.
noisewaterphd

0

Я намагаюся написати клас, який має "обидва" a listабо a 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

Останній підпункт оператора if, коли він ітерацію закінчується, other- це той, який мають на увазі більшість людей. Однак, як бачите, можна також мати keys()власність. Це в поєднанні з атрибутом __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все одно буде продовжувати працювати. Зверніть увагу, що, як правило, зі словниками, якщо ви додаєте до списку, він повертає КЛЮЧІ, а не значення, як вам потрібно. Це може бути несподівано для інших людей, які використовують ваш код, якщо не очевидно, чому це сталося.
Рік підтримує Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.