Dicționar Python din câmpurile unui obiect (Programare, Python, Dicționar, Atribute, Obiect, Metaprogramare)

Julio César a intrebat.

Știți dacă există o funcție încorporată pentru a construi un dicționar dintr-un obiect arbitrar? Aș vrea să fac ceva de genul acesta:

>>> class Foo:
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> props(f)
{ 'bar' : 'hello', 'baz' : 'world' }

NOTĂ: Nu ar trebui să includă metode. Doar câmpuri.

14 răspunsuri
user686868

Rețineți că cea mai bună practică în Python 2.7 este de a utiliza new-style clase (nu este necesar cu Python 3), adică

class Foo(object):
   ...

De asemenea, există o diferență între un „obiect” și o „clasă”. Pentru a construi un dicționar dintr-un dicționar arbitrar obiect, , este suficient să se folosească __dict__. De obicei, vă veți declara metodele la nivel de clasă și atributele la nivel de instanță, astfel încât __dict__ ar trebui să fie suficient. De exemplu:

>>> class A(object):
...   def __init__(self):
...     self.b = 1
...     self.c = 2
...   def do_nothing(self):
...     pass
...
>>> a = A()
>>> a.__dict__
{'c': 2, 'b': 1}

O abordare mai bună (sugerată de robert în comentarii) este cea a programului integrat vars funcție:

>>> vars(a)
{'c': 2, 'b': 1}

Alternativ, în funcție de ceea ce doriți să faceți, ar fi bine să moșteniți din dict. Atunci clasa dvs. este deja un dicționar și, dacă doriți, puteți suprascrie getattr și/sau setattr pentru a apela și a seta dicționarul. De exemplu:

class Foo(dict):
    def __init__(self):
        pass
    def __getattr__(self, attr):
        return self[attr]

    # etc...

Comentarii

  • Ce se întâmplă dacă unul dintre atributele lui A are un getter personalizat? (o funcție cu un decorator @property)? Apare în continuare în ____dict____? Care va fi valoarea sa? –  > Por zakdances.
  • __dict__ nu va funcționa dacă obiectul folosește sloturi (sau este definit într-un modul C). –  > Por Antimoniu.
  • Există un echivalent al acestei metode pentru obiectele de clasă? Adică, în loc să folosiți f=Foo() și apoi să faceți f.__dict__, faceți direct Foo.__dict__? –  > Por chiffa.
  • 55

  • Îmi pare rău, am ajuns târziu la această problemă, dar nu ar trebui să vars(a) face acest lucru? Pentru mine este de preferat să invocăm __dict__ direct. –  > Por robert.
  • pentru al doilea exemplu ar fi mai bine să se facă __getattr__ = dict.__getitem__ pentru a reproduce exact comportamentul, atunci ați dori, de asemenea, să __setattr__ = dict.__setitem__ și __delattr__ = dict.__delitem__ pentru a fi complet. –  > Por Tadhg McDonald-Jensen.
Berislav Lopac

În loc de x.__dict__, , este de fapt mai pythonic să se folosească vars(x).

Comentarii

  • De acord. Rețineți că puteți, de asemenea, să faceți conversia în celălalt sens (dict->class), tastând MyClass(**my_dict), , presupunând că ați definit un constructor cu parametri care reflectă atributele clasei. Nu este nevoie să accesați atributele private sau să suprascrieți dict. –  > Por tvt173.
  • Puteți explica de ce este mai pythonic? –  > Por Hugh W.
  • În primul rând, Python evită, în general, apelarea directă a elementelor dunder, existând aproape întotdeauna o metodă sau o funcție (sau un operator) pentru a le accesa indirect. În general, atributele și metodele dunder sunt un detaliu de implementare, iar utilizarea funcției „wrapper” vă permite să le separați pe cele două. În al doilea rând, în acest fel puteți suprascrie funcția vars funcție și să introduceți funcționalități suplimentare fără a modifica obiectul în sine. –  > Por Berislav Lopac.
  • Tot nu reușește dacă clasa dvs. utilizează __slots__ totuși. –  > Por c z.
  • Este corect, și am simțit întotdeauna că ar fi o direcție bună să extindem vars spre, adică să returneze un echivalent al lui __dict__ pentru clasele „cu sloturi”. Deocamdată, aceasta poate fi emulată prin adăugarea unui parametru __dict__ care returnează {x: getattr(self, x) for x in self.__slots__} (nu sunt sigur dacă acest lucru afectează în vreun fel performanța sau comportamentul). –  > Por Berislav Lopac.
dF.

The dir builtin vă va oferi toate atributele obiectului, inclusiv metode speciale precum __str__, , __dict__ și o grămadă de altele pe care probabil nu le doriți. Dar puteți face ceva de genul:

>>> class Foo(object):
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> [name for name in dir(f) if not name.startswith('__')]
[ 'bar', 'baz' ]
>>> dict((name, getattr(f, name)) for name in dir(f) if not name.startswith('__')) 
{ 'bar': 'hello', 'baz': 'world' }

Deci, puteți extinde acest lucru pentru a returna doar atributele de date și nu metodele, definind props funcția astfel:

import inspect

def props(obj):
    pr = {}
    for name in dir(obj):
        value = getattr(obj, name)
        if not name.startswith('__') and not inspect.ismethod(value):
            pr[name] = value
    return pr

Comentarii

  • Acest cod include metode. Există o modalitate de a exclude metodele? Am nevoie doar de câmpurile obiectului. Mulțumesc.  > Por Julio César.
  • ismethod nu prinde funcțiile. Exemplu: inspect.ismethod(str.upper). inspect.isfunction nu este mult mai util, totuși. Nu sunt sigur cum să abordez acest lucru imediat. –  > Por Ehtesh Choudhury.
  • Am făcut câteva modificări pentru a recurge crud și a ignora toate erorile la o adâncime aici, mulțumesc! gist.github.com/thorsummoner/bf0142fd24974a0ced778768a33a3069 –  > Por ThorSummoner.
Julio César

M-am mulțumit cu o combinație a ambelor răspunsuri:

dict((key, value) for key, value in f.__dict__.iteritems() 
    if not callable(value) and not key.startswith('__'))

Comentarii

  • Funcționează și asta, dar țineți cont de faptul că vă va oferi doar atributele setate pe instanță, nu și pe clasă (cum ar fi clasa Foo din exemplul dvs.)… –  > Por dF..
  • Deci, jcarrascal, este mai bine să înglobezi codul de mai sus într-o funcție precum props(), apoi poți apela fie props(f), fie props(Foo). Observați că aproape întotdeauna este mai bine să scrieți o funcție, decât să scrieți cod „inline”. –  > Por quamrana.
  • Frumos, btw rețineți că acest lucru este pentru python2.7, pentru python3 relpace iteritems() cu pur și simplu items(). –  > Por Morten.
  • Și cum rămâne cu staticmethod? Nu este callable. –  > Por Alex.
Seaux

M-am gândit să-mi fac puțin timp să vă arăt cum puteți traduce un obiect în dict prin intermediul dict(obj).

class A(object):
    d = '4'
    e = '5'
    f = '6'

    def __init__(self):
        self.a = '1'
        self.b = '2'
        self.c = '3'

    def __iter__(self):
        # first start by grabbing the Class items
        iters = dict((x,y) for x,y in A.__dict__.items() if x[:2] != '__')

        # then update the class items with the instance items
        iters.update(self.__dict__)

        # now 'yield' through the items
        for x,y in iters.items():
            yield x,y

a = A()
print(dict(a)) 
# prints "{'a': '1', 'c': '3', 'b': '2', 'e': '5', 'd': '4', 'f': '6'}"

Secțiunea cheie a acestui cod este __iter__ funcție.

După cum explică comentariile, primul lucru pe care îl facem este să luăm elementele de clasă și să prevenim tot ceea ce începe cu ‘__’.

Odată ce ați creat acel dict, , atunci puteți folosi funcția update dict și să introduceți instanța __dict__.

Acestea vă vor oferi un dicționar complet al membrilor clasei+instanță. Acum, tot ce rămâne de făcut este să iterați peste ele și să obțineți rezultatele.

De asemenea, dacă intenționați să folosiți acest lucru foarte mult, puteți crea un fișier @iterable decorator de clasă.

def iterable(cls):
    def iterfn(self):
        iters = dict((x,y) for x,y in cls.__dict__.items() if x[:2] != '__')
        iters.update(self.__dict__)

        for x,y in iters.items():
            yield x,y

    cls.__iter__ = iterfn
    return cls

@iterable
class B(object):
    d = 'd'
    e = 'e'
    f = 'f'

    def __init__(self):
        self.a = 'a'
        self.b = 'b'
        self.c = 'c'

b = B()
print(dict(b))

Comentarii

  • Acesta va prelua și el toate metodele, dar avem nevoie doar de câmpurile clasă+instanță. Poate dict((x, y) for x, y in KpiRow.__dict__.items() if x[:2] != '__' and not callable(y)) va rezolva problema? Dar tot ar putea exista static metode 🙁 –  > Por Alex.
indentare

Pentru a construi un dicționar dintr -un arbitrar obiect arbitrar, , este suficient să folosiți __dict__.

În acest fel, lipsesc atributele pe care obiectul le moștenește de la clasa sa. De exemplu,

class c(object):
    x = 3
a = c()

hasattr(a, ‘x’) este adevărat, dar ‘x’ nu apare în a.__dict__.

Comentarii

  • În acest caz, care este soluția? Deoarece vars() nu funcționează –  > Por should_be_working.
  • @should_be_working dir este soluția în acest caz. Consultați celălalt răspuns despre acest lucru. –  > Por Albert.
R H

Un dezavantaj al utilizării __dict__ este că este superficială; nu va converti nicio subclasă în dicționare.

Dacă utilizați Python3.5 sau o versiune mai recentă, puteți utiliza jsons:

>>> import jsons
>>> jsons.dump(f)
{'bar': 'hello', 'baz': 'world'}

Score_Under

Răspuns tardiv, dar furnizat pentru a fi complet și în beneficiul celor care caută pe Google:

def props(x):
    return dict((key, getattr(x, key)) for key in dir(x) if key not in dir(x.__class__))

Acest lucru nu va afișa metodele definite în clasă, dar va afișa în continuare câmpurile, inclusiv pe cele atribuite la lambdas sau pe cele care încep cu o subliniere dublă.

radtek

Cred că cea mai simplă metodă este să creați un fișier getitem pentru clasă. Dacă aveți nevoie să scrieți în obiect, puteți crea un atribut personalizat setattr . Iată un exemplu pentru getitem:

class A(object):
    def __init__(self):
        self.b = 1
        self.c = 2
    def __getitem__(self, item):
        return self.__dict__[item]

# Usage: 
a = A()
a.__getitem__('b')  # Outputs 1
a.__dict__  # Outputs {'c': 2, 'b': 1}
vars(a)  # Outputs {'c': 2, 'b': 1}

dict generează atributele obiectelor într-un dicționar, iar obiectul dicționar poate fi utilizat pentru a obține elementul de care aveți nevoie.

Comentarii

  • După acest răspuns, încă nu este clar cum se obține un dicționar dintr-un obiect. Nu proprietățile, ci întregul dicționar;) –  > Por maxkoryukov.
Ricky Levi

vars() este grozav, dar nu funcționează pentru obiecte imbricate de obiecte

Conversia obiectelor imbricate de obiecte în dict:

def to_dict(self):
    return json.loads(json.dumps(self, default=lambda o: o.__dict__))

coanor

Dacă doriți să enumerați o parte din atributele dumneavoastră, suprascrieți __dict__:

def __dict__(self):
    d = {
    'attr_1' : self.attr_1,
    ...
    }
    return d

# Call __dict__
d = instance.__dict__()

Acest lucru ajută foarte mult dacă instance obțineți niște date de blocuri mari și doriți să împingeți d către Redis ca o coadă de mesaje.

Comentarii

  • __dict__ este un atribut, nu o metodă, astfel încât acest exemplu modifică interfața (adică trebuie să o apelați ca o metodă apelabilă), deci nu o suprascrieți. –  > Por Berislav Lopac.
Anakhand

După cum s-a menționat într-unul dintre comentariile de mai sus, vars în prezent nu este universal, în sensul că nu funcționează pentru obiecte cu __slots__ în loc de un obiect normal __dict__. În plus, unele obiecte (de exemplu, builtins precum str sau int) au nici a __dict__ nici __slots__.

Deocamdată, o soluție mai versatilă ar putea fi aceasta:

def instance_attributes(obj: Any) -> Dict[str, Any]:
    """Get a name-to-value dictionary of instance attributes of an arbitrary object."""
    try:
        return vars(obj)
    except TypeError:
        pass

    # object doesn't have __dict__, try with __slots__
    try:
        slots = obj.__slots__
    except AttributeError:
        # doesn't have __dict__ nor __slots__, probably a builtin like str or int
        return {}
    # collect all slots attributes (some might not be present)
    attrs = {}
    for name in slots:
        try:
            attrs[name] = getattr(obj, name)
        except AttributeError:
            continue
    return attrs

Exemplu:

class Foo:
    class_var = "spam"


class Bar:
    class_var = "eggs"
    
    __slots__ = ["a", "b"]
>>> foo = Foo()
>>> foo.a = 1
>>> foo.b = 2
>>> instance_attributes(foo)
{'a': 1, 'b': 2}

>>> bar = Bar()
>>> bar.a = 3
>>> instance_attributes(bar)
{'a': 3}

>>> instance_attributes("baz") 
{}


Rant:

Este păcat că acest lucru nu este încorporat în vars deja. Multe dintre componentele construite în Python promit să fie „soluția” la o problemă, dar apoi există întotdeauna câteva cazuri speciale care nu sunt tratate… Și, în orice caz, se ajunge să trebuiască să se scrie codul manual.

Reed Sandberg

În 2021, și pentru obiecte/dicte/json imbricate folosiți pydantic BaseModel – va converti dicte imbricate și obiecte json imbricate în obiecte python și JSON și viceversa:

https://pydantic-docs.helpmanual.io/usage/models/

>>> class Foo(BaseModel):
...     count: int
...     size: float = None
... 
>>> 
>>> class Bar(BaseModel):
...     apple = 'x'
...     banana = 'y'
... 
>>> 
>>> class Spam(BaseModel):
...     foo: Foo
...     bars: List[Bar]
... 
>>> 
>>> m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])

Obiect în dict

>>> print(m.dict())
{'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}]}

Obiect în JSON

>>> print(m.json())
{"foo": {"count": 4, "size": null}, "bars": [{"apple": "x1", "banana": "y"}, {"apple": "x2", "banana": "y"}]}

Dict în obiect

>>> spam = Spam.parse_obj({'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y2'}]})
>>> spam
Spam(foo=Foo(count=4, size=None), bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y2')])

JSON în obiect

>>> spam = Spam.parse_raw('{"foo": {"count": 4, "size": null}, "bars": [{"apple": "x1", "banana": "y"}, {"apple": "x2", "banana": "y"}]}')
>>> spam
Spam(foo=Foo(count=4, size=None), bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')])

spattanaik75

PYTHON 3:

class DateTimeDecoder(json.JSONDecoder):

   def __init__(self, *args, **kargs):
        JSONDecoder.__init__(self, object_hook=self.dict_to_object,
                         *args, **kargs)

   def dict_to_object(self, d):
       if '__type__' not in d:
          return d

       type = d.pop('__type__')
       try:
          dateobj = datetime(**d)
          return dateobj
       except:
          d['__type__'] = type
          return d

def json_default_format(value):
    try:
        if isinstance(value, datetime):
            return {
                '__type__': 'datetime',
                'year': value.year,
                'month': value.month,
                'day': value.day,
                'hour': value.hour,
                'minute': value.minute,
                'second': value.second,
                'microsecond': value.microsecond,
            }
        if isinstance(value, decimal.Decimal):
            return float(value)
        if isinstance(value, Enum):
            return value.name
        else:
            return vars(value)
    except Exception as e:
        raise ValueError

Acum puteți utiliza codul de mai sus în interiorul propriei clase:

class Foo():
  def toJSON(self):
        return json.loads(
            json.dumps(self, sort_keys=True, indent=4, separators=(',', ': '), default=json_default_format), cls=DateTimeDecoder)


Foo().toJSON()