Python-Wörterbuch aus den Feldern eines Objekts


339

Wissen Sie, ob es eine integrierte Funktion zum Erstellen eines Wörterbuchs aus einem beliebigen Objekt gibt? Ich möchte so etwas machen:

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

HINWEIS: Es sollten keine Methoden enthalten sein. Nur Felder.

Antworten:


419

Beachten Sie, dass die beste Vorgehensweise in Python 2.7 darin besteht, Klassen neuen Stils zu verwenden (die in Python 3 nicht benötigt werden), d. H.

class Foo(object):
   ...

Es gibt auch einen Unterschied zwischen einem 'Objekt' und einer 'Klasse'. Um ein Wörterbuch aus einem beliebigen Objekt zu erstellen , ist es ausreichend, es zu verwenden __dict__. Normalerweise deklarieren Sie Ihre Methoden auf Klassenebene und Ihre Attribute auf Instanzebene. Dies __dict__sollte in Ordnung sein. Zum Beispiel:

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

Ein besserer Ansatz (von Robert in Kommentaren vorgeschlagen) ist die eingebaute varsFunktion:

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

Je nachdem, was Sie tun möchten, kann es auch hilfreich sein, von zu erben dict. Dann ist Ihre Klasse bereits ein Wörterbuch, und wenn Sie möchten, können Sie das Diktat überschreiben getattrund / oder setattrdurchrufen und festlegen. Zum Beispiel:

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

    # etc...

3
Was passiert, wenn eines der Attribute von A einen benutzerdefinierten Getter hat? (eine Funktion mit einem @property Dekorateur)? Wird es immer noch in ____dict____ angezeigt? Was wird sein Wert sein?
Zakdances

11
__dict__funktioniert nicht, wenn das Objekt Slots verwendet (oder in einem C-Modul definiert ist).
Antimon

1
Gibt es ein Äquivalent dieser Methode für die Klassenobjekte? IE Anstatt f = Foo () zu verwenden und dann f .__ dict__ auszuführen, direkt Foo .__ dict__?
Chiffa

49
Entschuldigung, ich komme so spät, sollte das aber nicht vars(a)tun? Für mich ist es vorzuziehen, das __dict__direkt aufzurufen .
Robert

2
Für das zweite Beispiel wäre es besser, __getattr__ = dict.__getitem__das Verhalten genau zu replizieren, __setattr__ = dict.__setitem__als __delattr__ = dict.__delitem__für die Vollständigkeit.
Tadhg McDonald-Jensen

140

Stattdessen x.__dict__ist es tatsächlich pythonischer zu verwenden vars(x).


3
Einverstanden. Beachten Sie, dass Sie durch Eingabe auch die andere Richtung (dict-> class) konvertieren können MyClass(**my_dict), vorausgesetzt, Sie haben einen Konstruktor mit Parametern definiert, die die Klassenattribute widerspiegeln. Sie müssen nicht auf private Attribute zugreifen oder Diktate überschreiben.
tvt173

2
Können Sie erklären, warum es mehr pythonisch ist?
Hugh W

1
Erstens meidet Python Aufrufe von Dunder-Elementen im Allgemeinen direkt, und es gibt fast immer eine Methode oder Funktion (oder einen Operator), um indirekt darauf zuzugreifen. Im Allgemeinen sind Dunder-Attribute und -Methoden ein Implementierungsdetail, und mit der Funktion "Wrapper" können Sie die beiden trennen. Zweitens können Sie auf diese Weise die varsFunktion überschreiben und zusätzliche Funktionen einführen, ohne das Objekt selbst zu ändern.
Berislav Lopac

1
Es schlägt immer noch fehl, wenn Ihre Klasse verwendet __slots__.
cz

Das ist richtig, und ich hatte immer das Gefühl, dass es eine gute Richtung wäre, sich darauf auszudehnen vars, dh ein Äquivalent von __dict__für "geschlitzte" Klassen zurückzugeben. Derzeit kann es durch Hinzufügen einer zurückgegebenen __dict__Eigenschaft emuliert werden {x: getattr(self, x) for x in self.__slots__}(nicht sicher, ob dies die Leistung oder das Verhalten in irgendeiner Weise beeinflusst).
Berislav Lopac

59

Die direingebaute gibt Ihnen alle Objektattribute, einschließlich speziellen Methoden wie __str__, __dict__und eine ganze Reihe von anderen , die Sie wahrscheinlich nicht wollen. Aber Sie können so etwas tun wie:

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

Sie können dies also erweitern, um nur Datenattribute und keine Methoden zurückzugeben, indem Sie Ihre propsFunktion folgendermaßen definieren :

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

1
Dieser Code enthält Methoden. Gibt es eine Möglichkeit, Methoden auszuschließen? Ich brauche nur die Felder des Objekts. Vielen Dank
Julio César

ismethodfängt keine Funktionen ab. Beispiel : inspect.ismethod(str.upper). inspect.isfunctionist jedoch nicht viel hilfreicher. Ich bin mir nicht sicher, wie ich das sofort angehen soll.
Ehtesh Choudhury

Ich habe einige Änderungen vorgenommen, um grob wiederzukehren und alle Fehler hier bis in die Tiefe zu ignorieren, danke! gist.github.com/thorsummoner/bf0142fd24974a0ced778768a33a3069
ThorSummoner

26

Ich habe mich mit einer Kombination aus beiden Antworten zufrieden gegeben:

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

Das funktioniert auch, aber beachten Sie, dass Sie nur die für die Instanz festgelegten Attribute erhalten, nicht für die Klasse (wie in Ihrem Beispiel die Klasse Foo) ...
dF.

Also, jcarrascal, Sie sollten den obigen Code besser in eine Funktion wie props () einbinden, dann können Sie entweder Requisiten (f) oder Requisiten (Foo) aufrufen. Beachten Sie, dass Sie fast immer besser dran sind, eine Funktion zu schreiben, als Inline-Code zu schreiben.
Quamrana

Schön, übrigens, dies ist für Python2.7, für Python3 Relpace iteritems () mit einfach items ().
Morten

Und was ist mit staticmethod? Es ist nicht callable.
Alex

16

Ich dachte, ich würde einige Zeit brauchen, um Ihnen zu zeigen, wie Sie ein Objekt übersetzen können, über das Sie diktieren möchten 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'}"

Der Schlüsselabschnitt dieses Codes ist die __iter__Funktion.

Wie die Kommentare erklären, greifen wir zuerst die Klassenelemente zu und verhindern alles, was mit '__' beginnt.

Sobald Sie das erstellt haben, dictkönnen Sie die updateDiktierfunktion verwenden und die Instanz übergeben __dict__.

Diese geben Ihnen ein vollständiges Klassen- + Instanzwörterbuch der Mitglieder. Jetzt müssen sie nur noch durchlaufen und die Renditen erzielen.

Wenn Sie dies häufig verwenden möchten, können Sie auch einen @iterableKlassendekorateur erstellen .

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))

Dadurch werden auch alle Methoden erfasst, es werden jedoch nur Felder für Klasse + Instanz benötigt. Vielleicht dict((x, y) for x, y in KpiRow.__dict__.items() if x[:2] != '__' and not callable(y))wird es gelöst? Aber es könnte immer noch staticMethoden geben :(
Alex

15

Um ein Wörterbuch aus einem beliebigen Objekt zu erstellen , ist es ausreichend, es zu verwenden __dict__.

Dabei fehlen Attribute, die das Objekt von seiner Klasse erbt. Zum Beispiel,

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

hasattr (a, 'x') ist wahr, aber 'x' erscheint nicht in einem .__ dict__


In diesem Fall, was ist die Lösung? Da vars()funktioniert nicht
should_be_working

@should_be_working dirist in diesem Fall die Lösung. Siehe die andere Antwort dazu.
Albert

8

Späte Antwort, aber zur Vollständigkeit und zum Nutzen der Googler vorgesehen:

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

Hier werden keine in der Klasse definierten Methoden angezeigt, aber es werden weiterhin Felder angezeigt, einschließlich der Lambdas zugewiesenen oder derjenigen, die mit einem doppelten Unterstrich beginnen.


6

Ich denke, der einfachste Weg ist, ein getitem- Attribut für die Klasse zu erstellen . Wenn Sie in das Objekt schreiben müssen, können Sie ein benutzerdefiniertes Setattr erstellen . Hier ist ein Beispiel für 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 generiert die Objektattribute in einem Wörterbuch und das Wörterbuchobjekt kann verwendet werden, um das gewünschte Element abzurufen.


Nach dieser Antwort ist immer noch nicht klar, wie man ein Wörterbuch von einem Objekt erhält. Nicht Eigenschaften, sondern ganzes Wörterbuch;)
Maxkoryukov

6

Ein Nachteil der Verwendung __dict__ ist, dass es flach ist; Es werden keine Unterklassen in Wörterbücher konvertiert.

Wenn Sie Python3.5 oder höher verwenden, können Sie Folgendes verwenden jsons:

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

3

Wenn Sie einen Teil Ihrer Attribute auflisten möchten, überschreiben Sie __dict__:

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

# Call __dict__
d = instance.__dict__()

Dies ist sehr hilfreich, wenn Sie instancegroße dBlockdaten erhalten und wie eine Nachrichtenwarteschlange an Redis senden möchten .


__dict__ist ein Attribut, keine Methode, daher ändert dieses Beispiel die Schnittstelle (dh Sie müssen sie als aufrufbar aufrufen), sodass sie nicht überschrieben wird.
Berislav Lopac

0

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

Jetzt können Sie den obigen Code in Ihrer eigenen Klasse verwenden:

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() 

0

vars() ist großartig, funktioniert aber nicht für verschachtelte Objekte von Objekten

Konvertieren Sie verschachtelte Objekte von Objekten in Diktate:

def to_dict(self):
    return json.loads(json.dumps(self, default=lambda o: o.__dict__))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.