Iterieren Sie über Objektattribute in Python


162

Ich habe ein Python-Objekt mit mehreren Attributen und Methoden. Ich möchte über Objektattribute iterieren.

class my_python_obj(object):
    attr1='a'
    attr2='b'
    attr3='c'

    def method1(self, etc, etc):
        #Statements

Ich möchte ein Wörterbuch generieren, das alle Objektattribute und ihre aktuellen Werte enthält, aber ich möchte dies auf dynamische Weise tun (wenn ich später ein weiteres Attribut hinzufüge, muss ich nicht daran denken, auch meine Funktion zu aktualisieren).

In PHP können Variablen als Schlüssel verwendet werden, aber Objekte in Python sind nicht beschreibbar. Wenn ich hierfür die Punktnotation verwende, wird ein neues Attribut mit dem Namen meiner Variable erstellt, was nicht meine Absicht ist.

Nur um die Dinge klarer zu machen:

def to_dict(self):
    '''this is what I already have'''
    d={}
    d["attr1"]= self.attr1
    d["attr2"]= self.attr2
    d["attr3"]= self.attr3
    return d

· ·

def to_dict(self):
    '''this is what I want to do'''
    d={}
    for v in my_python_obj.attributes:
        d[v] = self.v
    return d

Update: Mit Attributen meine ich nur die Variablen dieses Objekts, nicht die Methoden.



@sean Insbesondere stackoverflow.com/a/1251789/4203 ist der richtige Weg. Sie würden das optionale predicateArgument verwenden, um eine aufrufbare Datei zu übergeben, die (gebundene?) Funktionen herausfiltert (wodurch die Methoden entfernt werden).
Hank Gay

Antworten:


227

Angenommen, Sie haben eine Klasse wie

>>> class Cls(object):
...     foo = 1
...     bar = 'hello'
...     def func(self):
...         return 'call me'
...
>>> obj = Cls()

Wenn Sie dirdas Objekt aufrufen, erhalten Sie alle Attribute dieses Objekts zurück, einschließlich der speziellen Python-Attribute. Obwohl einige Objektattribute aufrufbar sind, wie z. B. Methoden.

>>> dir(obj)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo', 'func']

Sie können die speziellen Methoden jederzeit mithilfe eines Listenverständnisses herausfiltern.

>>> [a for a in dir(obj) if not a.startswith('__')]
['bar', 'foo', 'func']

oder wenn Sie Karte / Filter bevorzugen.

>>> filter(lambda a: not a.startswith('__'), dir(obj))
['bar', 'foo', 'func']

Wenn Sie die Methoden herausfiltern möchten, können Sie die integrierte Methode callableals Prüfung verwenden.

>>> [a for a in dir(obj) if not a.startswith('__') and not callable(getattr(obj, a))]
['bar', 'foo']

Sie können auch den Unterschied zwischen Ihrer Klasse und ihrem Instanzobjekt mithilfe von untersuchen.

>>> set(dir(Cls)) - set(dir(object))
set(['__module__', 'bar', 'func', '__dict__', 'foo', '__weakref__'])

1
Dies ist eine schlechte Idee, ich würde sie nicht einmal vorschlagen, aber wenn Sie wollen, sollten Sie zumindest die Einschränkung vorsehen.
Julian

2
@Meitham @Pablo Was falsch ist, ist hauptsächlich, dass Sie dies nicht tun müssen. Welche Attribute Sie interessieren, sollte inklusive und nicht exklusiv sein . Wie Sie bereits gesehen haben, schließen Sie Methoden ein, und jeder Versuch, sie auszuschließen, ist fehlerhaft, da sie böse Dinge wie callableoder beinhalten types.MethodType. Was passiert, wenn ich ein Attribut namens "_foo" hinzufüge, in dem ein Zwischenergebnis oder eine interne Datenstruktur gespeichert ist? Was passiert, wenn ich der Klasse harmlos ein Attribut hinzufüge name, z. B., und jetzt plötzlich wird es aufgenommen?
Julian

3
@Julian der obige Code wählt beide aus _foound nameweil sie jetzt Attribute des Objekts sind. Wenn Sie sie nicht einschließen möchten, können Sie sie in der Listenverständnisbedingung ausschließen. Der obige Code ist eine gängige Python-Redewendung und eine beliebte Methode zur Überprüfung von Python-Objekten.
Meitham

30
Eigentlich ist obj .__ dict__ (wahrscheinlich) besser für diesen Zweck.
Dave

5
@Julian dir()"lügt" nur, wenn Metaklassen (ein Fall mit extremen Kanten) oder Klassen mit Attributen übergeben werden, die durch @DynamicClassAttribute(ein weiterer Fall mit extremen Kanten) gekennzeichnet sind. In beiden Fällen wird dies durch Aufrufen des dirs()Wrappers inspect.getmembers()behoben. Für Standardobjekte reicht jedoch der Listenverständnisansatz dieser Lösung zum Herausfiltern nicht aufrufbarer Elemente absolut aus. Dass einige Pythonisten es als "schlechte Idee" bezeichnen würden, verblüfft mich, aber ... für jeden seine eigene, nehme ich an?
Cecil Curry

57

Fügen Sie im Allgemeinen eine __iter__Methode in Ihre Klasse ein und durchlaufen Sie die Objektattribute oder fügen Sie diese Mixin-Klasse in Ihre Klasse ein.

class IterMixin(object):
    def __iter__(self):
        for attr, value in self.__dict__.iteritems():
            yield attr, value

Deine Klasse:

>>> class YourClass(IterMixin): pass
...
>>> yc = YourClass()
>>> yc.one = range(15)
>>> yc.two = 'test'
>>> dict(yc)
{'one': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 'two': 'test'}

Dies funktioniert nur, wenn oneund danach twodefiniert wird yc = YourClass(). Die Frage fragt nach dem Durchlaufen des vorhandenen Attris in YourClass(). Außerdem ist das Erben der IterMixinKlasse möglicherweise nicht immer als Lösung verfügbar.
Xiao

4
vars(yc)gibt das gleiche Ergebnis, ohne von einer anderen Klasse erben zu müssen.
Nathan

1
Mein obiger Kommentar ist nicht ganz richtig; vars(yc)ist fast dasselbe, aber das Wörterbuch, das es Ihnen zurückgibt, gehört der Instanz. __dict__Wenn Sie es ändern, wird es in der Instanz wiedergegeben. Dies kann zu Problemen führen, wenn Sie nicht vorsichtig sind. Daher ist die oben beschriebene Methode manchmal besser (obwohl das Kopieren des Wörterbuchs wahrscheinlich einfacher ist). Beachten Sie außerdem, dass das oben zurückgegebene Wörterbuch zwar nicht das der Instanz ist __dict__, die Werte jedoch dieselben sind, sodass das Ändern von veränderlichen Werten weiterhin in den Attributen der Instanz berücksichtigt wird.
Nathan

25

Wie bereits in einigen Antworten / Kommentaren erwähnt, speichern Python-Objekte bereits ein Wörterbuch ihrer Attribute (Methoden sind nicht enthalten). Auf diese kann zugegriffen werden __dict__, aber der bessere Weg ist die Verwendung vars(die Ausgabe ist jedoch dieselbe). Beachten Sie, dass durch Ändern dieses Wörterbuchs die Attribute der Instanz geändert werden! Dies kann nützlich sein, bedeutet aber auch, dass Sie bei der Verwendung dieses Wörterbuchs vorsichtig sein sollten. Hier ist ein kurzes Beispiel:

class A():
    def __init__(self, x=3, y=2, z=5):
        self.x = x
        self._y = y
        self.__z__ = z

    def f(self):
        pass

a = A()
print(vars(a))
# {'x': 3, '_y': 2, '__z__': 5}
# all of the attributes of `a` but no methods!

# note how the dictionary is always up-to-date
a.x = 10
print(vars(a))
# {'x': 10, '_y': 2, '__z__': 5}

# modifying the dictionary modifies the instance attribute
vars(a)["_y"] = 20
print(vars(a))
# {'x': 10, '_y': 20, '__z__': 5}

Die Verwendung dir(a)ist eine seltsame, wenn nicht geradezu schlechte Herangehensweise an dieses Problem. Es ist gut, wenn Sie wirklich alle Attribute und Methoden der Klasse durchlaufen müssen (einschließlich der speziellen Methoden wie __init__). Dies scheint jedoch nicht das zu sein, was Sie wollen, und selbst die akzeptierte Antwort geht schlecht dahin, indem Sie eine spröde Filterung anwenden, um zu versuchen, Methoden zu entfernen und nur die Attribute zu belassen. Sie können sehen, wie dies für die Klasse fehlschlagen würdeA oben definierte .

(Die Verwendung __dict__wurde in einigen Antworten durchgeführt, aber alle definieren unnötige Methoden, anstatt sie direkt zu verwenden. Nur ein Kommentar schlägt die Verwendung vor. vars)


1
Mit dem vars () -Ansatz habe ich nicht herausgefunden, wie mit der Situation umgegangen werden soll, in der die Klasse A ein Mitglied hat, das ein anderes Objekt ist, dessen Typ eine benutzerdefinierte Klasse B ist. Vars (a) scheint __repr __ () aufzurufen. auf seinem Mitglied vom Typ B. __repr __ () soll, wie ich verstehe, eine Zeichenfolge zurückgeben. Aber wenn ich vars (a) aufrufe, scheint es sinnvoll zu sein, dass dieser Aufruf ein verschachteltes Diktat anstelle eines Diktats mit einer Zeichenfolgendarstellung von B.
plafratt

1
Wenn Sie a.bals eine benutzerdefinierte Klasse Bdann vars(a)["b"] is a.b, wie man erwarten würde; nichts anderes würde wirklich Sinn machen (denken Sie an a.bsyntaktischen Zucker für a.__dict__["b"]). Wenn Sie haben d = vars(a)und aufrufen repr(d), wird es repr(d["b"])als Teil der Rückgabe seiner eigenen Repr-Zeichenfolge aufgerufen, da nur die Klasse Bwirklich weiß, wie es als Zeichenfolge dargestellt werden soll.
Nathan

2

Objekte in Python speichern ihre Attribute (einschließlich Funktionen) in einem Diktat namens __dict__. Sie können (sollten dies aber im Allgemeinen nicht) verwenden, um direkt auf die Attribute zuzugreifen. Wenn Sie nur eine Liste möchten, können Sie auch anrufendir(obj) , die eine Iterable mit allen Attributnamen zurückgibt, an die Sie dann übergeben können getattr.

Es ist jedoch normalerweise ein schlechtes Design, irgendetwas mit den Namen der Variablen tun zu müssen. Warum sie nicht in einer Sammlung aufbewahren?

class Foo(object):
    def __init__(self, **values):
        self.special_values = values

Sie können dann mit über die Tasten iterieren for key in obj.special_values:


Können Sie etwas näher erläutern, wie ich die Sammlung verwenden würde, um das zu erreichen, was ich möchte?
Pablo Mescher

1
Ich füge dem Objekt keine Attribute dynamisch hinzu. Die Variablen, die ich habe, werden für lange Zeit gleich bleiben, aber ich denke, es wäre schön, so etwas tun zu können, wie ich es vorhabe.
Pablo Mescher

1
class someclass:
        x=1
        y=2
        z=3
        def __init__(self):
           self.current_idx = 0
           self.items = ["x","y","z"]
        def next(self):
            if self.current_idx < len(self.items):
                self.current_idx += 1
                k = self.items[self.current_idx-1]
                return (k,getattr(self,k))
            else:
                raise StopIteration
        def __iter__(self):
           return self

dann nenne es einfach als iterable

s=someclass()
for k,v in s:
    print k,"=",v

Das scheint zu kompliziert. Wenn ich keine Wahl habe, bleibe ich lieber bei dem, was ich gerade mache.
Pablo Mescher

0

Die richtige Antwort darauf ist, dass Sie nicht sollten. Wenn Sie diese Art von Dingen möchten, verwenden Sie entweder nur ein Diktat oder Sie müssen einem Container explizit Attribute hinzufügen. Sie können dies automatisieren, indem Sie sich mit Dekorateuren vertraut machen.

Insbesondere Methode1 in Ihrem Beispiel ist übrigens genauso gut für ein Attribut.


2
Ja, ich weiß, ich sollte nicht ... aber es hilft mir zu verstehen, wie die Dinge funktionieren. Ich komme aus einem PHP-Hintergrund und habe diese Art von Dingen täglich gemacht
Pablo Mescher

Du hast mich überzeugt. Die Aktualisierung der to_dict () -Methode ist weitaus einfacher als jede bisherige Antwort.
Pablo Mescher

1
Gibt es noch Menschen, die keinen dogmatischen Rat geben? Dynamische Programmierung wird Sie verbrennen, wenn Sie nicht vorsichtig sind, aber die Funktionen in der zu verwendenden Sprache sind.
Henrik Vendelbo

0

Für Python 3.6

class SomeClass:

    def attr_list(self, should_print=False):

        items = self.__dict__.items()
        if should_print:
            [print(f"attribute: {k}    value: {v}") for k, v in items]

        return items

6
Können Sie Ihren Posts eine Erklärung hinzufügen, damit auch nicht professionelle Pythonisten von Ihren Antworten profitieren können?
not2qubit

-3

Für alle pythonischen Eiferer da draußen bin ich sicher, dass Johan Cleeze Ihren Dogmatismus gutheißen würde;). Ich lasse diese Antwort immer wieder herabsetzen. Das macht mich selbstbewusster. Hinterlasse einen Kommentar du Hühner!

Für Python 3.6

class SomeClass:

    def attr_list1(self, should_print=False):

        for k in self.__dict__.keys():
            v = self.__dict__.__getitem__(k)
            if should_print:
                print(f"attr: {k}    value: {v}")

    def attr_list(self, should_print=False):

        b = [(k, v) for k, v in self.__dict__.items()]
        if should_print:
            [print(f"attr: {a[0]}    value: {a[1]}") for a in b]
        return b

Gibt es einen Grund, warum Sie zwei Antworten auf diese Frage haben?
Nathan

@ Nathan gibt es, weil einer ausführlicher und der andere pythonischer ist. Ich wusste auch nicht, was schneller läuft, also entschied ich mich, den Leser wählen zu lassen.
Rubber Duck

@ Nathan bist du Python oder Stack Overflow Polizei? Es gibt verschiedene Codierungsstile. Ich bevorzuge eine nicht pythonische, weil ich in vielen Sprachen und Technologien arbeite und ich denke, dass sie eher eigenwillig ist. Trotzdem ist das Verständnis von Iist cool und prägnant. Warum also nicht dem aufgeklärten Leser beides leisten?
Rubber Duck

Für alle pythonischen Eiferer da draußen bin ich sicher, dass Johan Cleeze Ihren Dogmatismus gutheißen würde;). Ich lasse diese Antwort immer wieder herabsetzen. Das macht mich selbstbewusster. Hinterlasse einen Kommentar du Hühner!
Rubber Duck
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.