W Pythonie, jak rzutować obiekt klasy na dyktando


85

Powiedzmy, że mam prostą klasę w Pythonie

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}

Bardzo łatwo mogę rzucić to na liczbę całkowitą

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

Który jest świetny! Ale teraz chcę rzucić to na dyktando w podobny sposób

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

Co muszę zdefiniować, aby to zadziałało? Próbowałem zastępując zarówno __dict__i dictna __what_goes_here__, ale dict(w)spowodowało TypeError: Wharrgarbl object is not iterablew obydwu przypadkach. Nie sądzę, aby po prostu uczynienie klasy iterowalną rozwiąże problem. Próbowałem też używać wielu googlów z tyloma różnymi sformułowaniami „obiekt rzutowany w języku Python do dyktowania”, o ile mogłem wymyślić, ale nie mogłem znaleźć niczego odpowiedniego: {

Również! Zauważ, że dzwonienie w.__dict__nie zrobi tego, czego chcę, ponieważ będzie zawierać w.versioni w.sum. Chcę dostosować obsadę dictw taki sam sposób, w jaki mogę dostosować obsadę int, używając def int(self).

Wiem, że mógłbym po prostu zrobić coś takiego

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

Ale zakładam, że istnieje pythoniczny sposób na wykonanie dict(w)pracy, ponieważ jest to ten sam typ rzeczy, co int(w)lub str(w). Jeśli nie ma bardziej pytonicznego sposobu, to też jest w porządku, pomyślałem, że zapytam. O! Wydaje mi się, że to ma znaczenie, dotyczy to Pythona 2.7, ale super punkty bonusowe za stare i zepsute rozwiązanie 2.4.

Jest jeszcze jedno pytanie Przeciążanie __dict __ () w klasie pythona, która jest podobna do tej, ale może być na tyle inna, że ​​gwarantuje, że nie jest to duplikat. Uważam, że OP pyta, jak przesłać wszystkie dane z obiektów swojej klasy jako słowniki. Szukam bardziej spersonalizowanego podejścia, ponieważ nie chcę, aby wszystko __dict__zawarte w słowniku zostało zwrócone przez dict(). Coś w rodzaju zmiennych publicznych i prywatnych może wystarczyć, aby wyjaśnić, czego szukam. Obiekty będą przechowywać pewne wartości używane w obliczeniach i takie, że nie muszę / chcę pojawiać się w wynikowych słownikach.

AKTUALIZACJA: Zdecydowałem się pójść na asdictsugerowaną trasę, ale wybór tego, co chciałem być odpowiedzią na pytanie, był trudny. Zarówno @RickTeachey, jak i @ jpmc26 dostarczyły odpowiedzi, z którą zamierzam skorzystać, ale ten pierwszy miał więcej informacji i opcji, a także wylądował na tym samym wyniku i został bardziej pozytywnie oceniony, więc poszedłem z nim. Jednak głosy poparcia dookoła i dzięki za pomoc. Czaiłem się długo i ciężko na przepełnieniu stosu i staram się bardziej zanurzyć palce w wodzie.


1

@GarrettR Jest to pomocne, ale tak naprawdę nie ujawnia zbyt wielu informacji na temat tego konkretnego problemu.
Zizouz212


@RickTeachey z pewnością jest podobne do pytania Overloading ..., chociaż szukam bardziej konfigurowalnej reprezentacji dyktowania. Na przykład obiekt może przechowywać rzeczy, których nie chcę widzieć w dyktandzie.
upodobanie

Twoje pytanie jest myląco sformułowane, nie chcesz konwertować ani rzutować obiektu, aby dyktował reprezentację (tak, że możesz całkowicie odzyskać obiekt po konwersji z powrotem). Chcesz tylko utworzyć widok reprezentacji dyktowania dla (powiedzmy) zmiennych instancji publicznej.
smci

Odpowiedzi:


117

Istnieje co najmniej pięć sześć sposobów. Preferowany sposób zależy od twojego przypadku użycia.

Opcja 1: Po prostu dodaj asdict()metodę.

Opierając się na opisie problemu, bardzo bym się zastanowił nad asdictsposobem postępowania sugerowanym przez inne odpowiedzi. Dzieje się tak, ponieważ nie wygląda na to, że Twój obiekt jest naprawdę dużą kolekcją:

class Wharrgarbl(object):

    ...

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

Korzystanie z innych opcji poniżej może być mylące dla innych, chyba że jest bardzo oczywiste, które elementy składowe obiektu będą, a które nie byłyby iterowane lub określone jako pary klucz-wartość.

Opcja 1a: Dziedzicz swoją klasę z 'typing.NamedTuple'(lub w większości równoważną 'collections.namedtuple') i użyj dostarczonej _asdictmetody .

from typing import NamedTuple

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

Korzystanie z imieniem krotki to bardzo wygodny sposób, aby dodać wiele funkcjonalności do swojej klasie przy minimum wysiłku, w tym w _asdictmetodzie . Jednak ograniczenie polega na tym, że, jak pokazano powyżej, NT będzie obejmował wszystkich członków _asdict.

Jeśli są członkowie, których nie chcesz uwzględniać w swoim słowniku, musisz zmodyfikować _asdictwynik:

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

Innym ograniczeniem jest to, że NT jest tylko do odczytu. Może to być pożądane lub nie.

Opcja 2: Wdrożenie __iter__.

W ten sposób na przykład:

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

Teraz możesz po prostu:

dict(my_object)

To działa, ponieważ dict()konstruktor akceptuje iterowalne (key, value)pary w celu skonstruowania słownika. Zanim to zrobisz, zadaj sobie pytanie, czy iterowanie obiektu jako szeregu par kluczy i wartości w ten sposób - choć wygodne do tworzenia dict- może w rzeczywistości być zaskakującym zachowaniem w innych kontekstach. Np. Zadaj sobie pytanie „jakie powinno być zachowanie list(my_object)…?”

Ponadto należy pamiętać, że dostęp do wartości bezpośrednio przy użyciu obj["a"]składni get item nie będzie działać, a rozpakowywanie argumentów słów kluczowych nie będzie działać. W takim przypadku musisz zaimplementować protokół mapowania.

Wariant 3: Realizacja z protokołem odwzorowania . Pozwala to na zachowanie dostępu według klucza, rzutowanie na a dictbez użycia __iter__, a także zapewnia zachowanie podczas rozpakowywania ( {**my_obj}) i rozpakowywanie słów kluczowych, jeśli wszystkie klucze są ciągami ( dict(**my_obj)).

Protokół mapowania wymaga podania (co najmniej) dwóch metod jednocześnie: keys()i __getitem__.

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

Teraz możesz:

>>> 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'}

Jak wspomniano powyżej, jeśli używasz wystarczająco nowej wersji Pythona, możesz również rozpakować swój obiekt mapping-protocol do wyrażenia słownikowego, takiego jak ten (iw tym przypadku nie jest wymagane, aby twoje klucze były ciągami):

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

Należy zauważyć, że protokół mapowanie ma pierwszeństwo w __iter__sposób podczas odlewania przedmiotu do dictbezpośrednio (bez użycia kwarg rozpakowaniu tj dict(m)). Jest więc możliwe - i czasami wygodne - spowodowanie, aby obiekt zachowywał się inaczej, gdy jest używany jako iterowalny (np. list(m)), A gdy jest rzutowany na a dict( dict(m)).

PODKREŚLAJĄC : To, że MOŻESZ używać protokołu mapowania, NIE oznacza, że POWINIENEŚ to zrobić . Czy rzeczywiście ma sens przekazywanie obiektu jako zestawu par klucz-wartość, czy też jako argumenty i wartości słów kluczowych? Czy dostęp do niego za pomocą klucza - podobnie jak w słowniku - naprawdę ma sens?

Jeśli odpowiedź na te pytania jest twierdząca , prawdopodobnie warto rozważyć następną opcję.

Opcja 4: przyjrzyj się użyciu 'collections.abcmodułu „ .

Dziedziczenie Twojej klasy od 'collections.abc.Mappinglub 'collections.abc.MutableMappingsygnalizuje innym użytkownikom, że pod każdym względem Twoja klasa jest mapowaniem * i można oczekiwać, że będzie się w ten sposób zachowywać.

Nadal możesz rzucić swój obiekt na dictdokładnie taki, jaki potrzebujesz, ale prawdopodobnie nie miałoby to żadnego powodu. Ze względu na pisanie kaczych kłopotów z rzutowaniem obiektu mapującego na a dictbyłoby przez większość czasu dodatkowym, niepotrzebnym krokiem.

Ta odpowiedź może być również pomocna.

Jak zauważono w komentarzach poniżej: warto wspomnieć, że robienie tego w sposób abc zasadniczo zmienia twoją klasę obiektów w klasę dictpodobną do a (zakładając, że używasz, MutableMappinga nie Mappingklasy bazowej tylko do odczytu ). Wszystko, z czym byłbyś w stanie zrobić dict, mógłbyś zrobić z własnym obiektem klasy. Może to być pożądane lub nie.

Weź również pod uwagę numeryczne abcs w numbersmodule:

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

Ponieważ rzucasz również swój obiekt na obiekt int, bardziej sensowne może być przekształcenie swojej klasy w pełnoprawną int, aby rzucanie nie było konieczne.

Opcja 5: przyjrzyj się używaniu dataclassesmodułu (tylko Python 3.7), który zawiera wygodną asdict()metodę narzędziową.

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)

Teraz możesz to zrobić:

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

Opcja 6: Użyj typing.TypedDict, która została dodana w Pythonie 3.8 .

UWAGA: opcja 6 prawdopodobnie NIE jest tym, czego szuka OP lub inni czytelnicy w oparciu o tytuł tego pytania. Zobacz dodatkowe komentarze poniżej.

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

Używając tej opcji, wynikowy obiekt to adict (wyróżnienie: to nie jest a Wharrgarbl). Nie ma żadnego powodu, by „przesyłać” to na dyktando (chyba że robisz kopię).

A ponieważ obiekt to adict , sygnatura inicjalizacji jest identyczna jak w dicti jako taka akceptuje tylko argumenty słów kluczowych lub inny słownik.

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

Podkreślenie : powyższa „klasa” Wharrgarblnie jest w rzeczywistości nową klasą. Jest to po prostu cukier syntaktyczny do tworzenia dictobiektów wpisanych z polami różnych typów dla narzędzia do sprawdzania typów.

Jako taka, ta opcja może być całkiem wygodna do sygnalizowania czytelnikom twojego kodu (a także kontrolerom typów, takim jak mypy), że dictoczekuje się, że taki obiekt będzie miał określone klucze o określonych typach wartości.

Ale to oznacza, że ​​nie możesz na przykład dodać innych metod, chociaż możesz spróbować:

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

... ale to nie zadziała:

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

* „Mapowanie” stało się standardową „nazwą” dicttypu kaczki


1
Jest to sposób na uzyskanie efektu, którego chce PO, ale może powinieneś wyjaśnić, że ma on inne efekty, np. for k, v in my_objectBędzie również włączony.
zwol

@RickTeachey Widzę, do czego zmierzasz w punkcie asdict. Z pewnością nie potrzebuję obiektów, aby były iterowalne.
upodobanie

Dlaczego możemy po prostu użyć funkcji vars, jak wspomniano w innym linku /programming/61517/python-dictionary-from-an-objects-fields. Czy brakuje mi czegoś w tym pytaniu?
użytkownik

@user pytanie PO chcieli tylko członkowie a, bi cw dict. varsFunkcja zwróci słownika ze wszystkimi członkami obiektów w tym te, które nie zostały chcieli: suma version. W niektórych przypadkach varszadziała, ale z mojego doświadczenia wynika, że ​​nie jest to idiomatyczny pyton. Zwykle ludzie są bardziej dosadni, mówiąc „Rzucam teraz mój obiekt do słownika”, wykonując np obj.asdict(). W varsprzypadku zwykle jest to coś więcej niż obj_kwargs = vars(obj)następuje MyObject(**obj_kwargs). Innymi słowy: varsjest używany głównie do kopiowania obiektów.
Rick wspiera Monikę

co powiesz na dodanie metody, która zwraca self.__dict__?
user32882

17

Nie ma magicznej metody, która zrobi to, co chcesz. Odpowiedź brzmi: po prostu nazwij to odpowiednio. asdictto rozsądny wybór w przypadku zwykłej konwersji na dict, zainspirowany przede wszystkim namedtuple. Jednak twoja metoda będzie oczywiście zawierała specjalną logikę, której nazwa może nie być od razu oczywista; zwracasz tylko podzbiór stanu klasy. Jeśli możesz wymyślić nieco bardziej szczegółową nazwę, która jasno komunikuje koncepcje, tym lepiej.

Inne odpowiedzi sugerują użycie __iter__, ale jeśli twój obiekt nie jest naprawdę iterowalny (reprezentuje serię elementów), to naprawdę nie ma sensu i stanowi niezręczne nadużycie metody. Fakt, że chcesz odfiltrować część stanu klasy, czyni to podejście jeszcze bardziej wątpliwym.


tak, zwracanie podzbioru stanu całej klasy, odpowiednich wartości, jest tym, co próbuję osiągnąć. Mam zapisane pewne „ukryte” wartości, które przyspieszają obliczenia / porównania itp., Ale nie są one niczym, co człowiek chciałby zobaczyć lub z którymi chciałby się bawić, a także tak naprawdę nie reprezentują samego obiektu.
upodobanie

@ tr3buchet W przykładowym kodzie te wartości są przekazywane do inicjatora . Czy tak jest w twoim prawdziwym kodzie? Wydaje się dziwne, że obliczenia zapisane w pamięci podręcznej byłyby przekazywane jako argumenty.
jpmc26

wcale nie, zrobiłem to tylko po to, aby dać przykład
upodobanie

Chcę tylko zauważyć, że dokumenty wydają się akceptować używanie, __iter__jak sugerowały inne odpowiedzi: „W przypadku mapowań powinien iterować po kluczach kontenera i powinien być również dostępny jako metoda iterkeys ()”.
dbenton

@dbenton To nie jest to, co robi OP ani to, co zalecają inne odpowiedzi. Obiekt OP nie jest „kontenerem”, który w tym kontekście jest zasadniczo używany jako synonim kolekcji. Przedmiotem PO również nie jest to, co zwykle rozumie się przez mapowanie. Mapowanie to coś, co ma dowolną liczbę kluczy i wartości i powinno rozszerzać Mappingklasę bazową. Obiekt, który ma określone nazwane atrybuty, nie należy do tej kategorii.
jpmc26

11

coś takiego prawdopodobnie by zadziałało

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)

interesujące! iterowalny z krotek, nie rozważałem tego. Zrobię szybki test
upodobanie

Ponadto, zwrócenie iterowi listy krotek powoduje zrzut wszystkich wartości x, y i z do listy, na której jest oparty iter. W przypadku, gdy trzy instrukcje yield są możliwe (małe okienko, ale mimo to) uruchomienie tego w środowisku wątkowym może spowodować, że inny wątek zmodyfikuje atrybut y lub atrybuty z w taki sposób, że raportowane x, y i z nie są spójne.
PaulMcG

@PaulMcGuire: czy CPython gwarantuje, że wszystkie będą zgodne nawet z kodem Jorana? Pomyślałbym, że można zaplanować kolejny wątek, na przykład po utworzeniu pierwszej krotki z x, ale przed drugą krotką z y. Czy się mylę, a właściwie rozumienie listy jest atomowe w odniesieniu do planowania wątków?
Steve Jessop

Teraz, kiedy pytasz, zdaję sobie sprawę, że nawet rozumienie listy nie jest gwarantowane atomowo, ponieważ przełącznik wątków Pythona robi to na poziomie kodu bajtowego, a nie na poziomie źródła. Ale twoja ekspozycja jest tylko na czas tworzenia listy - po utworzeniu listy wszelkie aktualizacje właściwości nie będą miały znaczenia (chyba że wartości właściwości są obiektami zmiennymi). Najlepiej używać dismodułu do przeglądania aktualnych kodów bajtowych.
PaulMcG

11

Myślę, że to zadziała dla Ciebie.

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)

Wynik

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


co się stało z sumą i wersją? czym różni się to od zwykłego uzyskiwania dostępu __dict__tak, jak sugerowano w innej odpowiedzi?
Joran Beasley

to nie jest dyktando. wywołanie iteritems zwraca iterator
Garrett R

1
Podoba mi się ta odpowiedź ... ale wymaga trochę więcej pracy, zanim spełni wymagania OP ...
Joran Beasley

ta odpowiedź tak naprawdę nie różni się od @JoranBeasley. Różnica polega na tym, że jego jest selektywny, podczas gdy ty chwyta całość, self.__dict__która zawiera elementy, których nie chcę mieć w słownikowej wersji obiektu.
upodobanie

6

Podobnie jak wiele innych, sugerowałbym zaimplementowanie funkcji to_dict () zamiast (lub oprócz) umożliwienia rzutowania do słownika. Myślę, że jest to bardziej oczywiste, że klasa obsługuje tego rodzaju funkcje. Możesz łatwo zaimplementować taką metodę:

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

Trudno powiedzieć, nie znając całego kontekstu problemu, ale nie przesłoniłbym __iter__.

Wdrożyłbym __what_goes_here__na zajęciach.

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

Nie, w tym są przechowywane zmienne.
Zizouz212,

3
Szczególnie zwróciłem uwagę, że w.__dict__nie robi tego, czego potrzebuję.
skłonność

Ale __dict__czy nie można tego było obejść?
noisewaterphd

@noisewaterphd nie, to jest magiczna metoda (może można by ją przesłonić, ale jej implikacje są co najmniej przerażające)
Joran Beasley

1
Dlaczego nie odziedziczyć po dict, a może jeszcze lepiej, po prostu utwórz metodę na klasie, aby zwrócić ją jako dict.
noisewaterphd

0

Próbuję napisać klasę, która jest „jednocześnie” a listlub a dict. Chcę, aby programista mógł zarówno „rzutować” ten obiekt na obiekt a list(upuszczając klawisze) lub dict(za pomocą klawiszy).

Patrząc na sposób, w jaki Python obecnie wykonuje dict()rzutowanie: wywołuje Mapping.update()z przekazanym obiektem. Oto kod z repozytorium Pythona :

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

otherWiększość ludzi ma na myśli ostatnią podrozdział instrukcji if, w której jest iterowane . Jak jednak widać, istnieje również możliwość posiadania keys()nieruchomości. To w połączeniu z a __getitem__()powinno ułatwić prawidłowe rzutowanie podklasy do słownika:

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)

Wtedy to zadziała:

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

Zaimplementowałeś protokół mapowania. Zobacz opcję 3 w mojej odpowiedzi powyżej. Jeśli chcesz również listupuszczać klucze, możesz dodać __iter__metodę, która iteruje przez wartości, a rzutowanie na dictnadal będzie działać. Zauważ, że zwykle w przypadku słowników, jeśli rzutujesz na listę, zwróci to KLUCZE, a nie żądane wartości. Może to być zaskakujące dla innych osób używających Twojego kodu, chyba że jest oczywiste, dlaczego tak się dzieje.
Rick wspiera Monikę
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.