417 Stimmen

Zugriff auf Diktatschlüssel wie ein Attribut?

Ich finde es bequemer, auf die Diktatschlüssel als obj.foo anstelle von obj['foo'] also habe ich diesen Ausschnitt geschrieben:

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value

Ich nehme jedoch an, dass es einen Grund dafür geben muss, dass Python diese Funktionalität nicht von Haus aus bereitstellt. Was wären die Vorbehalte und Fallstricke beim Zugriff auf dict-Schlüssel auf diese Weise?

416voto

Kimvais Punkte 36440

Aktualisierung - 2020

Seit diese Frage vor fast zehn Jahren gestellt wurde, hat sich in Python selbst einiges getan.

Während der Ansatz in meiner ursprünglichen Antwort ist immer noch gültig für einige Fälle, (z. B. Legacy-Projekte auf ältere Versionen von Python und Fälle, in denen Sie wirklich brauchen, um Dictionaries mit sehr dynamischen String-Schlüssel zu behandeln), ich denke, dass im Allgemeinen die Datenklassen die in Python 3.7 eingeführt wurden, sind die offensichtliche/richtige Lösung für die große Mehrheit der Anwendungsfälle von AttrDict .

Ursprüngliche Antwort

Dies geschieht am besten auf diese Weise:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

Einige Vorteile:

  • Es funktioniert tatsächlich!
  • Es werden keine Methoden der Wörterbuchklasse beschattet (z. B. .keys() funktionieren sehr gut. Es sei denn, Sie weisen ihnen einen Wert zu (siehe unten).
  • Attribute und Artikel sind immer synchronisiert
  • Der Versuch, auf einen nicht existierenden Schlüssel als Attribut zuzugreifen, führt zu AttributeError anstelle von KeyError
  • Unterstützt [Tab] Autovervollständigung (z. B. in Jupyter und Ipython)

Nachteile:

  • Methoden wie .keys() wird no funktionieren problemlos, wenn sie durch eingehende Daten überschrieben werden
  • Verursacht eine Speicherleck in Python < 2.7.4 / Python3 < 3.2.3
  • Pylint wird verrückt mit E1123(unexpected-keyword-arg) et E1103(maybe-no-member)
  • Für den Uneingeweihten erscheint es wie pure Magie.

Eine kurze Erklärung, wie das funktioniert

  • Alle Python-Objekte speichern ihre Attribute intern in einem Wörterbuch, das den Namen __dict__ .
  • Es ist nicht erforderlich, dass das interne Wörterbuch __dict__ müsste "nur ein einfaches Diktat" sein, damit wir jede Unterklasse von dict() in das interne Wörterbuch.
  • In unserem Fall weisen wir einfach die AttrDict() Instanz, die wir instanziieren (wie in __init__ ).
  • Durch einen Anruf super() 's __init__() Methode haben wir sichergestellt, dass sie sich (bereits) genau wie ein Wörterbuch verhält, da diese Funktion alle Wörterbuchinstanziierung Code.

Ein Grund, warum Python diese Funktionalität nicht von Haus aus bietet

Wie in der Liste der "Nachteile" erwähnt, kombiniert dies den Namensraum der gespeicherten Schlüssel (die aus beliebigen und/oder nicht vertrauenswürdigen Daten stammen können!) mit dem Namensraum der eingebauten dict-Methodenattribute. Zum Beispiel:

d = AttrDict()
d.update({'items':["jacket", "necktie", "trousers"]})
for k, v in d.items():    # TypeError: 'list' object is not callable
    print "Never reached!"

128voto

Hery Punkte 7065

Sie können alle zulässigen Zeichenfolgen als Teil des Schlüssels haben, wenn Sie die Array-Notation verwenden. Zum Beispiel, obj['!#$%^&*()_']

96voto

Deacon Punkte 3377

In dem ich die Frage beantworte, die mir gestellt wurde

Warum bietet Python das nicht von Haus aus?

Ich vermute, dass es mit der Tatsache zu tun hat, dass Zen der Python : "Es sollte einen - und vorzugsweise nur einen - offensichtlichen Weg geben, dies zu tun." Dies würde zwei offensichtliche Wege schaffen, um auf Werte aus Wörterbüchern zuzugreifen: obj['key'] et obj.key .

Vorbehalte und Fallstricke

Dazu gehören mögliche Unklarheiten und Verwirrungen im Code, z. B. könnte Folgendes für jemanden verwirrend sein sonst der Ihren Code zu einem späteren Zeitpunkt pflegen wird, oder sogar an Sie, wenn Sie eine Zeit lang nicht mehr damit arbeiten werden. Nochmals, von Zen : "Lesbarkeit zählt!"

>>> KEY = 'spam'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1

Si d instanziiert wird oder KEY ist definiert oder d[KEY] wird weit weg von dem Ort zugewiesen, an dem d.spam verwendet wird, kann dies leicht zu Verwirrung darüber führen, was getan wird, da es sich hierbei nicht um eine allgemein gebräuchliche Redewendung handelt. Ich weiß, dass es das Potenzial hat, mich zu verwirren.

Wenn Sie außerdem den Wert von KEY wie folgt (aber nicht ändern d.spam ), erhalten Sie jetzt:

>>> KEY = 'foo'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'C' object has no attribute 'spam'

IMO ist das den Aufwand nicht wert.

Andere Artikel

Wie bereits erwähnt, können Sie jedes beliebige hashfähige Objekt (nicht nur eine Zeichenkette) als Diktatschlüssel verwenden. Zum Beispiel,

>>> d = {(2, 3): True,}
>>> assert d[(2, 3)] is True
>>> 

ist legal, aber

>>> C = type('C', (object,), {(2, 3): True})
>>> d = C()
>>> assert d.(2, 3) is True
  File "<stdin>", line 1
  d.(2, 3)
    ^
SyntaxError: invalid syntax
>>> getattr(d, (2, 3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
>>> 

ist nicht. Damit haben Sie Zugriff auf den gesamten Bereich druckbarer Zeichen oder anderer hashbarer Objekte für Ihre Wörterbuchschlüssel, was beim Zugriff auf ein Objektattribut nicht der Fall ist. Dies ermöglicht solche Zaubereien wie eine zwischengespeicherte Objekt-Metaklasse, wie das Rezept aus der Python Kochbuch (Kap. 9) .

Wo ich ein Editorial schreibe

Ich bevorzuge die Ästhetik von spam.eggs über spam['eggs'] (ich finde, es sieht sauberer aus), und ich habe wirklich angefangen, mich nach dieser Funktionalität zu sehnen, als ich die namedtuple . Aber die Bequemlichkeit, Folgendes tun zu können, übertrumpft sie.

>>> KEYS = 'spam eggs ham'
>>> VALS = [1, 2, 3]
>>> d = {k: v for k, v in zip(KEYS.split(' '), VALS)}
>>> assert d == {'spam': 1, 'eggs': 2, 'ham': 3}
>>>

Dies ist ein einfaches Beispiel, aber ich verwende Dicts häufig in anderen Situationen als ich sie verwenden würde obj.key Notation (z. B. wenn ich Voreinstellungen aus einer XML-Datei einlesen muss). In anderen Fällen, in denen ich versucht bin, eine dynamische Klasse zu instanziieren und ihr aus ästhetischen Gründen ein paar Attribute hinzuzufügen, verwende ich aus Konsistenzgründen weiterhin ein Diktat, um die Lesbarkeit zu verbessern.

Ich bin mir sicher, dass der Auftraggeber dieses Problem längst zu seiner Zufriedenheit gelöst hat, aber wenn er diese Funktionalität immer noch haben möchte, dann schlage ich vor, dass er eines der Pakete von pypi herunterlädt, das sie bereitstellt:

  • Bündel ist diejenige, mit der ich mehr vertraut bin. Unterklasse von dict Damit haben Sie alle diese Funktionen.
  • AttrDict scheint auch ziemlich gut zu sein, aber ich bin nicht so vertraut damit und habe die Quelle nicht so detailliert durchgesehen Bündel .
  • Süchtig Wird aktiv gepflegt und bietet attr-ähnlichen Zugang und mehr.
  • Wie in den Kommentaren von Rotareti erwähnt, ist Bunch veraltet, aber es gibt einen aktiven Fork namens Munch .

Um die Lesbarkeit seines Codes zu verbessern, empfehle ich jedoch dringend, dass er no seine Notationsstile mischen. Wenn er diese Notation bevorzugt, sollte er einfach ein dynamisches Objekt instanziieren, ihm die gewünschten Attribute hinzufügen und das Ganze abhaken:

>>> C = type('C', (object,), {})
>>> d = C()
>>> d.spam = 1
>>> d.eggs = 2
>>> d.ham = 3
>>> assert d.__dict__ == {'spam': 1, 'eggs': 2, 'ham': 3}

Darin aktualisiere ich, um eine Folgefrage in den Kommentaren zu beantworten

In den Kommentaren (unten), Elmo fragt:

Und wenn Sie noch einen Schritt weiter gehen wollen? ( Bezug nehmend auf Typ(...) )

Ich habe diesen Anwendungsfall zwar noch nie benutzt (auch hier neige ich dazu, verschachtelte dict für Konsistenz), funktioniert der folgende Code:

>>> C = type('C', (object,), {})
>>> d = C()
>>> for x in 'spam eggs ham'.split():
...     setattr(d, x, C())
...     i = 1
...     for y in 'one two three'.split():
...         setattr(getattr(d, x), y, i)
...         i += 1
...
>>> assert d.spam.__dict__ == {'one': 1, 'two': 2, 'three': 3}

91voto

slacy Punkte 10859

En Diese andere SO-Frage gibt es ein großartiges Implementierungsbeispiel, das Ihren bestehenden Code vereinfacht. Wie wäre es damit?

class AttributeDict(dict):
    __slots__ = () 
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__

Es ist viel übersichtlicher und lässt keinen Raum für zusätzlichen Ballast, der in Ihre __getattr__ et __setattr__ Funktionen in der Zukunft.

33voto

lindyblackburn Punkte 1505

Sie können eine geeignete Containerklasse aus der Standardbibliothek ziehen:

from argparse import Namespace

um zu vermeiden, dass Code-Bits herumkopiert werden müssen. Kein standardmäßiger Zugriff auf das Wörterbuch, aber man kann es leicht zurückerhalten, wenn man es wirklich will. Der Code in argparse ist einfach,

class Namespace(_AttributeHolder):
    """Simple object for storing attributes.

    Implements equality by attribute names and values, and provides a simple
    string representation.
    """

    def __init__(self, **kwargs):
        for name in kwargs:
            setattr(self, name, kwargs[name])

    __hash__ = None

    def __eq__(self, other):
        return vars(self) == vars(other)

    def __ne__(self, other):
        return not (self == other)

    def __contains__(self, key):
        return key in self.__dict__

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X