Zugriff auf Diktatschlüssel wie ein Attribut?

303

Ich finde es bequemer, auf Diktierschlüssel zuzugreifen, als obj.fooauf 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 gehe jedoch davon aus, dass es einen Grund geben muss, warum Python diese Funktionalität nicht sofort bereitstellt. Was wären die Vorbehalte und Gefahren beim Zugriff auf Diktatschlüssel auf diese Weise?

Izz ad-Din Ruhulessin
quelle
16
Wenn Sie überall von einem Satz mit begrenzter Größe auf fest codierte Schlüssel zugreifen, ist es möglicherweise besser, Objekte zu erstellen, die diese enthalten. collections.namedtupleist dafür sehr nützlich.
6
stackoverflow.com/questions/3031219/… hat eine ähnliche Lösung, geht aber noch einen Schritt weiter
keflavich
1
Ein Modul dafür finden Sie unter github.com/bcj/AttrDict . Ich weiß nicht, wie es mit den Lösungen hier und in den damit verbundenen Fragen verglichen wird.
Matt Wilkie
Ich habe auch ähnliche Hacks verwendet, jetzt benutze icheasydict.EasyDict
Myon
Weitere Möglichkeiten, auf Wörterbuchmitglieder mit einem '.' : stackoverflow.com/questions/2352181/…
Punkt

Antworten:

304

Der beste Weg, dies zu tun, ist:

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

Einige Profis:

  • Es funktioniert tatsächlich!
  • Es sind keine Wörterbuchklassenmethoden im Schatten (z .keys(). B. funktionieren sie einwandfrei. Es sei denn, Sie weisen ihnen einen Wert zu, siehe unten).
  • Attribute und Elemente sind immer synchron
  • Der Versuch , nicht vorhandenen Schlüssel zuzugreifen , als ein Attribut richtig wirft AttributeErrorstattKeyError

Nachteile:

  • Methoden wie .keys()wird nicht gut funktionieren , wenn sie von eingehenden Daten überschrieben
  • Verursacht einen Speicherverlust in Python <2.7.4 / Python3 <3.2.3
  • Pylint geht Bananen mit E1123(unexpected-keyword-arg)undE1103(maybe-no-member)
  • Für die Uneingeweihten scheint es reine Magie zu sein.

Eine kurze Erklärung, wie das funktioniert

  • Alle Python-Objekte speichern ihre Attribute intern in einem benannten Wörterbuch __dict__.
  • Es ist nicht erforderlich, dass das interne Wörterbuch __dict__"nur ein einfaches Diktat" sein muss, damit wir dict()dem internen Wörterbuch eine beliebige Unterklasse zuweisen können .
  • In unserem Fall weisen wir einfach die AttrDict()Instanz zu, die wir instanziieren (wie wir uns gerade befinden __init__).
  • Durch Aufrufen super()der __init__()Methode haben wir sichergestellt, dass sie sich (bereits) genau wie ein Wörterbuch verhält, da diese Funktion den gesamten Wörterbuch-Instanziierungscode aufruft .

Ein Grund, warum Python diese Funktionalität nicht sofort bereitstellt

Wie in der Liste "Nachteile" angegeben, kombiniert dies den Namespace gespeicherter Schlüssel (die aus beliebigen und / oder nicht vertrauenswürdigen Daten stammen können!) Mit dem Namespace der eingebauten Diktatmethodenattribute. 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!"
Kimvais
quelle
1
Glauben Sie, dass das Memorry-Leck bei einem einfachen Objekt auftreten würde, wie: >>> Klasse MyD (Objekt): ... def init __ (self, d): ... self .__ dict = d
Rafe
Verursacht das Leck auch bei 2.7
pi.
1
Machen Sie das <= 2.7.3, da ich das benutze.
pi.
1
In den Versionshinweisen zu 2.7.4 wird dies behoben (nicht zuvor).
Robert Siemer
1
@viveksinghggits Nur weil Sie über das auf Dinge zugreifen ., können Sie die Regeln der Sprache nicht brechen :) Und ich möchte keine AttrDictraumhaltigen Felder automatisch in etwas anderes konvertieren.
Yurik
125

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

Hery
quelle
1
@ Izkata ja. lustige Sache an SE, dass es normalerweise eine "Top-Frage" gibt, dh. Titel und eine "unterste Frage", vielleicht weil SE nicht gerne hört, dass "Titel alles sagt"; Die "Vorbehalte" sind hier die untersten.
n611x007
2
Nicht, dass JavaScript ein besonders gutes Beispiel für eine Programmiersprache ist, aber Objekte in JS unterstützen sowohl den Attributzugriff als auch die Array-Notation, was den allgemeinen Fall erleichtert und einen allgemeinen Fallback für Symbole ermöglicht, die keine legalen Attributnamen sind.
André Caron
@Izkata Wie beantwortet dies die Frage? Diese Antwort besagt nur, dass Schlüssel einen beliebigen Namen haben können.
Melab
4
@Melab Die Frage ist What would be the caveats and pitfalls of accessing dict keys in this manner?(als Attribute) und die Antwort ist, dass die meisten der hier gezeigten Zeichen nicht verwendbar wären.
Izkata
83

Aus dieser anderen SO-Frage geht ein großartiges Implementierungsbeispiel hervor, das Ihren vorhandenen Code vereinfacht. Wie wäre es mit:

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

Viel prägnanter und lässt keinen Raum für zusätzliche Kruft, die in Ihre __getattr__und __setattr__Funktionen in der Zukunft gelangt .

Slacy
quelle
Könnten Sie AttributeDict.update oder AttributeDict.get mit dieser Methode aufrufen?
Dor
13
Beachten Sie, dass beim Hinzufügen neuer Attribute zur Laufzeit diese nicht dem Diktat selbst, sondern dem Diktatattribut hinzugefügt werden. ZB d = AttributeDict(foo=1). d.bar = 1Das Balkenattribut wird im Diktatattribut gespeichert , jedoch nicht im Diktat selbst. Beim Drucken dwird nur das foo-Element angezeigt.
P3trus
7
+1, weil es soweit ich das beurteilen kann perfekt funktioniert. @GringoSuave, @Izkata, @ P3trus Ich fordere jeden auf, zu behaupten, dass dies fehlschlägt. Zeigen Sie ein Beispiel, das nicht funktioniert d = AttributeDict(foo=1);d.bar = 1;print d=> {'foo': 1, 'bar': 1}Funktioniert für mich!
Dave Abrahams
4
@ DaveAbrahams Lesen Sie die vollständige Frage und sehen Sie sich die Antworten von Hery, Ryan und TheCommunistDuck an. Es geht nicht darum, wie das geht, sondern um Probleme, die auftreten können .
Izkata
6
Sie sollten eine __getattr__Methode bereitstellen , die eine AttributeErrorgetattr(obj, attr, default_value)default_valueattrobj
auslöst,
83

Worin ich die gestellte Frage beantworte

Warum bietet Python es nicht sofort an?

Ich vermute, dass es mit dem Zen von Python zu tun hat : "Es sollte einen - und vorzugsweise nur einen - offensichtlichen Weg geben, dies zu tun." Dies würde zwei offensichtliche Möglichkeiten für den Zugriff auf Werte aus Wörterbüchern schaffen: obj['key']undobj.key .

Vorsichtsmaßnahmen und Fallstricke

Dazu gehören mögliche Unklarheiten und Verwirrung im Code. dh könnte folgend jemand verwirrend sein sonst wer in wird Ihren Code zu einem späteren Zeitpunkt zu halten oder sogar zu Ihnen, wenn Sie nicht in es für eine Weile zurück. Nochmals aus dem Zen : "Lesbarkeit zählt!"

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

Wenn d instanziiert oder KEY definiert oder d[KEY] weit entfernt von wo zugewiesen wirdd.spam Verwendungsort wird, kann dies leicht zu Verwirrung darüber führen, was getan wird, da dies keine häufig verwendete Redewendung ist. Ich weiß, es könnte mich verwirren.

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

>>> 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, die Mühe nicht wert.

Andere Dinge

Wie andere angemerkt haben, können Sie jedes hashbare Objekt (nicht nur eine Zeichenfolge) 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. Auf diese Weise haben Sie Zugriff auf den gesamten Bereich druckbarer Zeichen oder anderer hashbarer Objekte für Ihre Wörterbuchschlüssel, über die Sie beim Zugriff auf ein Objektattribut nicht verfügen. Dies ermöglicht Magie wie eine zwischengespeicherte Objektmetaklasse, wie das Rezept aus dem Python-Kochbuch (Kap. 9) .

Worin ich redaktionell

Ich ziehe die Ästhetik spam.eggsüber spam['eggs'](ich glaube , es sauberer aussieht), und ich wirklich begonnen , diese Funktionalität Begierde , wenn ich das erfüllt namedtuple. Aber die Bequemlichkeit, die folgenden Aufgaben ausführen zu können, übertrifft 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 benutze häufig Diktate in anderen Situationen als ich obj.key Notation (dh wenn ich Einstellungen aus einer XML-Datei einlesen muss). In anderen Fällen, in denen ich aus ästhetischen Gründen versucht bin, eine dynamische Klasse zu instanziieren und einige Attribute darauf zu setzen, verwende ich weiterhin ein Diktat für die Konsistenz, um die Lesbarkeit zu verbessern.

Ich bin sicher, dass das OP dies längst zu seiner Zufriedenheit gelöst hat, aber wenn er diese Funktionalität weiterhin möchte, dann schlage ich vor, dass er eines der Pakete von pypi herunterlädt, die es bereitstellen:

  • Bunch ist derjenige, mit dem ich besser vertraut bin. Unterklasse vondict, damit Sie alle diese Funktionen haben.
  • AttrDict sieht auch so aus, als wäre es auch ziemlich gut, aber ich bin nicht so vertraut damit und habe die Quelle nicht so detailliert durchgesehen wie Bunch .
  • Addict wird aktiv gepflegt und bietet attr-ähnlichen Zugang und mehr.
  • Wie in den Kommentaren von Rotareti erwähnt, ist Bunch veraltet, aber es gibt eine aktive Gabel namens Munch .

Um die Lesbarkeit seines Codes zu verbessern, empfehle ich jedoch dringend, seine Notationsstile nicht zu mischen. Wenn er diese Notation bevorzugt, sollte er einfach ein dynamisches Objekt instanziieren, seine gewünschten Attribute hinzufügen und es einen Tag nennen:

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


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

In den Kommentaren (unten) fragt Elmo :

Was ist, wenn Sie tiefer gehen möchten? (bezogen auf Typ (...))

Obwohl ich diesen Anwendungsfall noch nie verwendet habe ( dictaus Konsistenzgründen verwende ich eher verschachtelte ), 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}
Doug R.
quelle
1
Bündel ist veraltet, aber es gibt eine aktive Gabelung davon: github.com/Infinidat/munch
Rotareti
@ Rotareti - Danke für das Heads-up! Dies ist keine Funktionalität, die ich benutze, daher war mir das nicht bewusst.
Doug R.
Was ist, wenn Sie tiefer gehen möchten? (unter Bezugnahme auf Typ (...))
Ole Aldric
6
Python ist wie ein umgekehrter Regenschirm, der bei starkem Regen hochgehalten wird. Zunächst sieht alles schick und funky aus, nach einiger Zeit wird es schwer, dann liest du plötzlich einige eingebaute Guru-Sachen auf SE und das Ganze kehrt mit der gesamten Nutzlast auf deinen Schultern zurück. Während du noch durchnässt bist, fühlst du dich leichter und alles ist so klar und erfrischt.
Ole Aldric
21

Vorsichtsmaßnahme: Aus bestimmten Gründen scheinen Klassen wie diese das Multiprozessor-Paket zu brechen. Ich hatte nur eine Weile mit diesem Fehler zu kämpfen, bevor ich diese SO fand: Ausnahme in Python-Multiprocessing finden

Ryan
quelle
19

Sie können eine praktische Containerklasse aus der Standardbibliothek abrufen:

from argparse import Namespace

um zu vermeiden, dass Codebits kopiert werden müssen. Kein Standardzugriff auf das Wörterbuch, aber es ist einfach, einen zurückzubekommen, wenn Sie es wirklich wollen. 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__
Lindyblackburn
quelle
2
PLUS 1 zum Verweisen auf eine Standardbibliothek, die den ersten Kommentar des OP adressiert.
Gordon Bean
4
Python enthält eine schnellere Klasse (in C implementiert) für diesen Fall: types.SimpleNamespace docs.python.org/dev/library/types.html#types.SimpleNamespace
Nuno André
18

Was wäre, wenn Sie einen Schlüssel wollten, der eine Methode wie __eq__oder war?__getattr__ ?

Und Sie könnten keinen Eintrag haben, der nicht mit einem Buchstaben beginnt 0343853 als Schlüssel nicht möglich.

Und was ist, wenn Sie keinen String verwenden möchten?

Die kommunistische Ente
quelle
In der Tat oder zum Beispiel andere Objekte als Schlüssel. Allerdings würde ich den Fehler daraus als "erwartetes Verhalten" klassifizieren - mit meiner Frage zielte ich mehr auf das Unerwartete.
Izz ad-Din Ruhulessin
pickle.dumpverwendet__getstate__
Cees Timmerman
12

Tupel können Diktiertasten verwendet werden. Wie würden Sie in Ihrem Konstrukt auf Tupel zugreifen?

Auch namedtuple ist eine komfortable Struktur , die Werte über das Attribut Zugriff zur Verfügung stellen kann.

Senthil Kumaran
quelle
7
Der Nachteil von Namedtuples ist, dass sie unveränderlich sind.
Izz ad-Din Ruhulessin
10
Einige würden sagen, dass Unveränderlichkeit kein Fehler ist, sondern ein Merkmal von Tupeln.
Ben Autor
9

Wie wäre es mit Prodict , der kleinen Python-Klasse, die ich geschrieben habe , um sie alle zu regieren :)

Außerdem erhalten Sie eine automatische Code-Vervollständigung , rekursive Objektinstanziierungen und eine automatische Typkonvertierung !

Sie können genau das tun, wonach Sie gefragt haben:

p = Prodict()
p.foo = 1
p.bar = "baz"

Beispiel 1: Tipphinweis

class Country(Prodict):
    name: str
    population: int

turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871

Auto-Code abgeschlossen

Beispiel 2: Automatische Typkonvertierung

germany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])

print(germany.population)  # 82175700
print(type(germany.population))  # <class 'int'>

print(germany.flag_colors)  # ['black', 'red', 'yellow']
print(type(germany.flag_colors))  # <class 'list'>
Ramazan Polat
quelle
2
wird auf python2 über pip installiert, funktioniert aber nicht auf python2
Ant6n
2
@ Ant6n erfordert Python 3.6+ wegen Typanmerkungen
Ramazan Polat
8

Es funktioniert im Allgemeinen nicht. Nicht alle gültigen Diktatschlüssel weisen adressierbare Attribute auf ("der Schlüssel"). Sie müssen also vorsichtig sein.

Python-Objekte sind im Grunde alle Wörterbücher. Ich bezweifle also, dass es viel Leistung oder andere Strafen gibt.

Tallseth
quelle
8

Dies geht nicht auf die ursprüngliche Frage ein, sollte aber für Leute nützlich sein, die wie ich hier landen, wenn sie nach einer Bibliothek suchen, die diese Funktionalität bietet.

Addict, es ist eine großartige Bibliothek dafür: https://github.com/mewwts/addict Es kümmert sich um viele Bedenken, die in früheren Antworten erwähnt wurden.

Ein Beispiel aus den Dokumenten:

body = {
    'query': {
        'filtered': {
            'query': {
                'match': {'description': 'addictive'}
            },
            'filter': {
                'term': {'created_by': 'Mats'}
            }
        }
    }
}

Mit Süchtigen:

from addict import Dict
body = Dict()
body.query.filtered.query.match.description = 'addictive'
body.query.filtered.filter.term.created_by = 'Mats'
Gonz
quelle
8

Ich habe mich gefragt, wie der aktuelle Status von "Diktatschlüsseln als attr" im Python-Ökosystem ist. Wie mehrere Kommentatoren hervorgehoben haben, ist dies wahrscheinlich nicht das, was Sie selbst von Grund auf neu rollen möchten , da es mehrere Fallstricke und Fußgewehre gibt, von denen einige sehr subtil sind. Auch würde ich nicht empfehlen, zu verwendenNamespace als Basisklasse Ich war auf diesem Weg, es ist nicht schön.

Glücklicherweise gibt es mehrere Open Source-Pakete, die diese Funktionalität bieten und für die Pip-Installation bereit sind! Leider gibt es mehrere Pakete. Hier ist eine Zusammenfassung vom Dezember 2019.

Konkurrenten (letzter Commit für Master | #commits | #contribs | Coverage%):

  • süchtig (28.04.2019 | 217 | 22 | 100%)
  • Munch (2019.12.16 | 160 | 17 |?%)
  • easydict (2018-10-18 | 51 | 6 |?%)
  • attrdict (2019-02-01 | 108 | 5 | 100%)
  • Produkt (2019-10-01 | 65 | 1 |?%)

Nicht mehr gewartet oder nicht mehr gewartet:

  • treedict (28.03.2014 | 95 | 2 |?%)
  • Haufen (2012-03-12 | 20 | 2 |?%)
  • NeoBunch

Ich empfehle derzeit munch oder süchtig . Sie haben die meisten Commits, Mitwirkenden und Releases und schlagen für jede eine gesunde Open-Source-Codebasis vor. Sie haben die sauberste readme.md, 100% Abdeckung und gut aussehende Tests.

Ich habe (vorerst!) Keinen Hund in diesem Rennen, außerdem habe ich meinen eigenen Diktat- / Attr-Code gewürfelt und eine Menge Zeit verschwendet, weil mir all diese Optionen nicht bekannt waren :). Ich könnte in Zukunft dazu beitragen, süchtig zu werden, da ich lieber ein solides Paket als ein paar fragmentierte sehen würde. Wenn Sie sie mögen, tragen Sie bei! Insbesondere sieht es so aus, als könnte Munch ein Codecov-Abzeichen und Addict ein Python-Versionsabzeichen verwenden.

Süchtige Profis:

  • rekursive Initialisierung (foo.abc = 'bar'), diktartige Argumente werden süchtig

süchtige Nachteile:

  • Schatten, typing.Dictwenn Siefrom addict import Dict
  • Keine Schlüsselüberprüfung. Wenn Sie einen Schlüssel falsch schreiben, erstellen Sie nur ein neues Attribut und nicht KeyError (danke AljoSt)

Munch-Profis:

  • eindeutige Benennung
  • integrierte ser / de-Funktionen für JSON und YAML

munch Nachteile:

  • Kein rekursiver init / kann jeweils nur einen attr initiieren

Worin ich redaktionell

Vor vielen Monden, als ich Texteditoren verwendete, um Python zu schreiben, mochte ich bei Projekten, die nur mit mir selbst oder einem anderen Entwickler durchgeführt wurden, den Stil von Diktatattrs und die Möglichkeit, Schlüssel durch einfaches Deklarieren einzufügen foo.bar.spam = eggs. Jetzt arbeite ich in Teams und verwende für alles eine IDE. Ich habe mich von diesen Arten von Datenstrukturen und der dynamischen Typisierung im Allgemeinen zu statischen Analysen, Funktionstechniken und Typhinweisen abgewandt. Ich habe angefangen, mit dieser Technik zu experimentieren und Pstruct mit Objekten meines eigenen Designs zu unterteilen:

class  BasePstruct(dict):
    def __getattr__(self, name):
        if name in self.__slots__:
            return self[name]
        return self.__getattribute__(name)

    def __setattr__(self, key, value):
        if key in self.__slots__:
            self[key] = value
            return
        if key in type(self).__dict__:
            self[key] = value
            return
        raise AttributeError(
            "type object '{}' has no attribute '{}'".format(type(self).__name__, key))


class FooPstruct(BasePstruct):
    __slots__ = ['foo', 'bar']

Auf diese Weise erhalten Sie ein Objekt, das sich immer noch wie ein Diktat verhält, aber Sie können auch viel starrer auf Schlüssel wie Attribute zugreifen. Der Vorteil hierbei ist, dass ich (oder die unglücklichen Konsumenten Ihres Codes) genau weiß, welche Felder existieren können und welche nicht, und die IDE kann Felder automatisch vervollständigen. Auch die Unterklassifizierung von Vanille dictbedeutet, dass die JSON-Serialisierung einfach ist. Ich denke, die nächste Entwicklung in dieser Idee wäre ein benutzerdefinierter Protobuf-Generator, der diese Schnittstellen emittiert, und ein netter Vorteil ist, dass Sie sprachübergreifende Datenstrukturen und IPC über gRPC nahezu kostenlos erhalten.

Wenn Sie sich für attr-dicts entscheiden, ist es wichtig zu dokumentieren, welche Felder für Ihre eigene Gesundheit (und die Ihrer Teamkollegen) erwartet werden.

Fühlen Sie sich frei, diesen Beitrag zu bearbeiten / zu aktualisieren, um ihn aktuell zu halten!

DeusXMachina
quelle
2
Ein großer Nachteil ist, dass beim Schreiben addicteines Attributs keine Ausnahmen Dictausgelöst werden, da ein neues Attribut zurückgegeben wird (dies ist erforderlich, damit foo.abc = 'bar' funktioniert).
AljoSt
5

Hier ist ein kurzes Beispiel für unveränderliche Datensätze mit integrierten Funktionen collections.namedtuple:

def record(name, d):
    return namedtuple(name, d.keys())(**d)

und ein Anwendungsbeispiel:

rec = record('Model', {
    'train_op': train_op,
    'loss': loss,
})

print rec.loss(..)
Ben Usman
quelle
5

Um der Antwort etwas Abwechslung zu verleihen, hat sci-kit learn Folgendes implementiert Bunch:

class Bunch(dict):                                                              
    """ Scikit Learn's container object                                         

    Dictionary-like object that exposes its keys as attributes.                 
    >>> b = Bunch(a=1, b=2)                                                     
    >>> b['b']                                                                  
    2                                                                           
    >>> b.b                                                                     
    2                                                                           
    >>> b.c = 6                                                                 
    >>> b['c']                                                                  
    6                                                                           
    """                                                                         

    def __init__(self, **kwargs):                                               
        super(Bunch, self).__init__(kwargs)                                     

    def __setattr__(self, key, value):                                          
        self[key] = value                                                       

    def __dir__(self):                                                          
        return self.keys()                                                      

    def __getattr__(self, key):                                                 
        try:                                                                    
            return self[key]                                                    
        except KeyError:                                                        
            raise AttributeError(key)                                           

    def __setstate__(self, state):                                              
        pass                       

Alles, was Sie brauchen, ist, die Methoden setattrund getattrzu erhalten - die getattrÜberprüfung auf Diktatschlüssel und die Überprüfung auf tatsächliche Attribute. Das setstaetist ein Update für Update für Beizen / Unpickling „Bündel“ - wenn inerested Check https://github.com/scikit-learn/scikit-learn/issues/6196

user2804865
quelle
3

Sie müssen keine eigenen schreiben, da setattr () und getattr () bereits vorhanden sind.

Der Vorteil von Klassenobjekten kommt wahrscheinlich bei der Definition und Vererbung von Klassen zum Tragen.

David W.
quelle
3

Ich habe dies basierend auf den Eingaben aus diesem Thread erstellt. Ich muss allerdings odict verwenden, also musste ich get überschreiben und attr setzen. Ich denke, dies sollte für die meisten speziellen Anwendungen funktionieren.

Die Verwendung sieht folgendermaßen aus:

# Create an ordered dict normally...
>>> od = OrderedAttrDict()
>>> od["a"] = 1
>>> od["b"] = 2
>>> od
OrderedAttrDict([('a', 1), ('b', 2)])

# Get and set data using attribute access...
>>> od.a
1
>>> od.b = 20
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])

# Setting a NEW attribute only creates it on the instance, not the dict...
>>> od.c = 8
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])
>>> od.c
8

Die Klasse:

class OrderedAttrDict(odict.OrderedDict):
    """
    Constructs an odict.OrderedDict with attribute access to data.

    Setting a NEW attribute only creates it on the instance, not the dict.
    Setting an attribute that is a key in the data will set the dict data but 
    will not create a new instance attribute
    """
    def __getattr__(self, attr):
        """
        Try to get the data. If attr is not a key, fall-back and get the attr
        """
        if self.has_key(attr):
            return super(OrderedAttrDict, self).__getitem__(attr)
        else:
            return super(OrderedAttrDict, self).__getattr__(attr)


    def __setattr__(self, attr, value):
        """
        Try to set the data. If attr is not a key, fall-back and set the attr
        """
        if self.has_key(attr):
            super(OrderedAttrDict, self).__setitem__(attr, value)
        else:
            super(OrderedAttrDict, self).__setattr__(attr, value)

Dies ist ein ziemlich cooles Muster, das bereits im Thread erwähnt wurde. Wenn Sie jedoch nur ein Diktat erstellen und es in ein Objekt konvertieren möchten, das mit der automatischen Vervollständigung in einer IDE funktioniert, usw.:

class ObjectFromDict(object):
    def __init__(self, d):
        self.__dict__ = d
Rafe
quelle
3

Anscheinend gibt es jetzt eine Bibliothek dafür - https://pypi.python.org/pypi/attrdict - die genau diese Funktionalität plus rekursives Zusammenführen und Laden von JSON implementiert. Könnte einen Blick wert sein.

Yurik
quelle
3

Das benutze ich

args = {
        'batch_size': 32,
        'workers': 4,
        'train_dir': 'train',
        'val_dir': 'val',
        'lr': 1e-3,
        'momentum': 0.9,
        'weight_decay': 1e-4
    }
args = namedtuple('Args', ' '.join(list(args.keys())))(**args)

print (args.lr)
Mujjiga
quelle
Dies ist eine gute schnelle und schmutzige Antwort. Meine einzige Beobachtung / Bemerkung ist, dass ich denke, dass der Namedtuple-Konstruktor eine Liste von Zeichenfolgen akzeptiert, so dass Ihre Lösung vereinfacht werden kann (glaube ich), um:namedtuple('Args', list(args.keys()))(**args)
Dan Nguyen
2

Sie können es mit dieser Klasse machen, die ich gerade gemacht habe. Mit dieser Klasse können Sie das MapObjekt wie ein anderes Wörterbuch (einschließlich JSON-Serialisierung) oder mit der Punktnotation verwenden. Ich hoffe dir zu helfen:

class Map(dict):
    """
    Example:
    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
    """
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                self[k] = v

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

Anwendungsbeispiele:

m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']
epool
quelle
1
Beachten Sie, dass es dictMethoden beschatten kann, zB: m=Map(); m["keys"] = 42; m.keys()gibt TypeError: 'int' object is not callable.
Bfontaine
@bfontaine Die Idee ist, eine Art field/attributeund keine zu sein method, aber wenn Sie einer Methode stattdessen eine Nummer zuweisen, können Sie mit dieser Methode darauf zugreifen m.method().
Epool
2

Lassen Sie mich eine weitere Implementierung veröffentlichen, die auf der Antwort von Kinvais aufbaut, aber Ideen aus dem in http://databio.org/posts/python_AttributeDict.html vorgeschlagenen AttributeDict integriert .

Der Vorteil dieser Version ist, dass sie auch für verschachtelte Wörterbücher funktioniert:

class AttrDict(dict):
    """
    A class to convert a nested Dictionary into an object with key-values
    that are accessible using attribute notation (AttrDict.attribute) instead of
    key notation (Dict["key"]). This class recursively sets Dicts to objects,
    allowing you to recurse down nested dicts (like: AttrDict.attr.attr)
    """

    # Inspired by:
    # http://stackoverflow.com/a/14620633/1551810
    # http://databio.org/posts/python_AttributeDict.html

    def __init__(self, iterable, **kwargs):
        super(AttrDict, self).__init__(iterable, **kwargs)
        for key, value in iterable.items():
            if isinstance(value, dict):
                self.__dict__[key] = AttrDict(value)
            else:
                self.__dict__[key] = value
Kadee
quelle
1
class AttrDict(dict):

     def __init__(self):
           self.__dict__ = self

if __name__ == '____main__':

     d = AttrDict()
     d['ray'] = 'hope'
     d.sun = 'shine'  >>> Now we can use this . notation
     print d['ray']
     print d.sun
h_vm
quelle
1

Lösung ist:

DICT_RESERVED_KEYS = vars(dict).keys()


class SmartDict(dict):
    """
    A Dict which is accessible via attribute dot notation
    """
    def __init__(self, *args, **kwargs):
        """
        :param args: multiple dicts ({}, {}, ..)
        :param kwargs: arbitrary keys='value'

        If ``keyerror=False`` is passed then not found attributes will
        always return None.
        """
        super(SmartDict, self).__init__()
        self['__keyerror'] = kwargs.pop('keyerror', True)
        [self.update(arg) for arg in args if isinstance(arg, dict)]
        self.update(kwargs)

    def __getattr__(self, attr):
        if attr not in DICT_RESERVED_KEYS:
            if self['__keyerror']:
                return self[attr]
            else:
                return self.get(attr)
        return getattr(self, attr)

    def __setattr__(self, key, value):
        if key in DICT_RESERVED_KEYS:
            raise AttributeError("You cannot set a reserved name as attribute")
        self.__setitem__(key, value)

    def __copy__(self):
        return self.__class__(self)

    def copy(self):
        return self.__copy__()
Bruno Rocha - Rochacbruno
quelle
1

Was wären die Vorbehalte und Gefahren beim Zugriff auf Diktatschlüssel auf diese Weise?

Wie @Henry vorschlägt, besteht ein Grund dafür, dass der Punktzugriff in Diktaten möglicherweise nicht verwendet wird, darin, dass Diktatschlüsselnamen auf Python-gültige Variablen beschränkt werden, wodurch alle möglichen Namen eingeschränkt werden.

Das Folgende sind Beispiele dafür, warum der Punktzugriff angesichts eines Diktats im Allgemeinen nicht hilfreich wäre d:

Gültigkeit

Die folgenden Attribute wären in Python ungültig:

d.1_foo                           # enumerated names
d./bar                            # path names
d.21.7, d.12:30                   # decimals, time
d.""                              # empty strings
d.john doe, d.denny's             # spaces, misc punctuation 
d.3 * x                           # expressions  

Stil

PEP8-Konventionen würden der Benennung von Attributen eine weiche Einschränkung auferlegen:

A. Reservierte Schlüsselwortnamen (oder integrierte Funktionsnamen):

d.in
d.False, d.True
d.max, d.min
d.sum
d.id

Wenn der Name eines Funktionsarguments mit einem reservierten Schlüsselwort kollidiert, ist es im Allgemeinen besser, einen einzelnen nachgestellten Unterstrich anzuhängen ...

B. Die Fallregel zu Methoden und Variablennamen :

Variablennamen folgen der gleichen Konvention wie Funktionsnamen.

d.Firstname
d.Country

Verwenden Sie die Regeln für die Benennung von Funktionen: Kleinbuchstaben mit durch Unterstriche getrennten Wörtern, um die Lesbarkeit zu verbessern.


Manchmal werden diese Bedenken in Bibliotheken wie Pandas geäußert , die den Punktzugriff auf DataFrame-Spalten nach Namen ermöglichen. Der Standardmechanismus zum Auflösen von Namensbeschränkungen ist auch die Array-Notation - eine Zeichenfolge in Klammern.

Wenn diese Einschränkungen für Ihren Anwendungsfall nicht gelten, gibt es verschiedene Optionen für Datenstrukturen mit gepunktetem Zugriff .

Pylang
quelle
1

Sie können dict_to_obj https://pypi.org/project/dict-to-obj/ verwenden. Es macht genau das, wonach Sie gefragt haben

From dict_to_obj import DictToObj
a = {
'foo': True
}
b = DictToObj(a)
b.foo
True
Alon Barad
quelle
1
Es ist eine gute Form, .ideabenutzerspezifische oder IDE-generierte Dateien in Ihre zu legen .gitignore.
DeusXMachina
1

Dies ist keine 'gute' Antwort, aber ich dachte, das wäre geschickt (es behandelt keine verschachtelten Diktate in der aktuellen Form). Wickeln Sie einfach Ihr Diktat in eine Funktion:

def make_funcdict(d=None, **kwargs)
    def funcdict(d=None, **kwargs):
        if d is not None:
            funcdict.__dict__.update(d)
        funcdict.__dict__.update(kwargs)
        return funcdict.__dict__
    funcdict(d, **kwargs)
    return funcdict

Jetzt haben Sie eine etwas andere Syntax. Zugriff auf die diktierten Elemente wie Attribute f.key. Um auf die Diktatelemente (und andere Diktiermethoden) wie gewohnt zuzugreifen, können Sie das Diktat f()['key']bequem aktualisieren, indem Sie f mit Schlüsselwortargumenten und / oder einem Wörterbuch aufrufen

Beispiel

d = {'name':'Henry', 'age':31}
d = make_funcdict(d)
>>> for key in d():
...     print key
... 
age
name
>>> print d.name
... Henry
>>> print d.age
... 31
>>> d({'Height':'5-11'}, Job='Carpenter')
... {'age': 31, 'name': 'Henry', 'Job': 'Carpenter', 'Height': '5-11'}

Und da ist es. Ich würde mich freuen, wenn jemand Vor- und Nachteile dieser Methode vorschlägt.

DylanYoung
quelle
0

Wie von Doug bemerkt, gibt es ein Bunch-Paket, mit dem Sie die obj.keyFunktionalität erreichen können. Eigentlich gibt es eine neuere Version namens

NeoBunch

Es hat jedoch eine großartige Funktion, mit der Sie Ihr Diktat über die Neobunchify- Funktion in ein NeoBunch-Objekt konvertieren können. Ich verwende häufig Mako-Vorlagen und die Übergabe von Daten als NeoBunch-Objekte macht sie weitaus lesbarer. Wenn Sie also zufällig ein normales Diktat in Ihrem Python-Programm verwenden, aber die Punktnotation in einer Mako-Vorlage möchten, können Sie sie folgendermaßen verwenden:

from mako.template import Template
from neobunch import neobunchify

mako_template = Template(filename='mako.tmpl', strict_undefined=True)
data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]}
with open('out.txt', 'w') as out_file:
    out_file.write(mako_template.render(**neobunchify(data)))

Und die Mako-Vorlage könnte folgendermaßen aussehen:

% for d in tmpl_data:
Column1     Column2
${d.key1}   ${d.key2}
% endfor
Mizu
quelle
Link zu NeoBunch ist 404
DeusXMachina
0

Der einfachste Weg ist, eine Klasse zu definieren. Nennen wir sie Namespace. welches das Objekt dict .update () auf dem dict verwendet. Dann wird das Diktat als Objekt behandelt.

class Namespace(object):
    '''
    helps referencing object in a dictionary as dict.key instead of dict['key']
    '''
    def __init__(self, adict):
        self.__dict__.update(adict)



Person = Namespace({'name': 'ahmed',
                     'age': 30}) #--> added for edge_cls


print(Person.name)
Elmahy
quelle