Inhalt des Destructuring-Bind-Wörterbuchs

84

Ich versuche, ein Wörterbuch zu "zerstören" und den Variablennamen nach seinen Schlüsseln Werte zuzuordnen. Etwas wie

params = {'a':1,'b':2}
a,b = params.values()

Da Wörterbücher jedoch nicht bestellt werden, gibt es keine Garantie dafür, dass params.values()Werte in der Reihenfolge von zurückgegeben werden (a, b). Gibt es eine gute Möglichkeit, dies zu tun?

hatmatrix
quelle
3
Faul? Vielleicht ... aber natürlich habe ich den einfachsten Fall zur Veranschaulichung gezeigt. Idealerweise wollte ich wie für x in params.items haben: eval ('% s =% f'% x), aber ich denke, eval () erlaubt keine Zuweisungen.
Hatmatrix
7
@JochenRitzel Ich bin mir ziemlich sicher, dass die meisten Benutzer von ES6 (JavaScript) die neue Syntax zur Objektzerstörung mögen : let {a, b} = params. Es verbessert die Lesbarkeit und stimmt vollständig mit dem Zen überein, über das Sie sprechen möchten.
Andy
8
@Andy I Liebe Objekt Destrukturierung in JS. Was für eine saubere, einfache und lesbare Möglichkeit, einige Schlüssel aus einem Diktat zu extrahieren. Ich kam hierher in der Hoffnung, in Python etwas Ähnliches zu finden.
Rotareti
2
Ich mag auch die Destrukturierung von ES6-Objekten, aber ich befürchte, dass dies in Python aus demselben Grund nicht funktioniert, aus dem das Map-Objekt von ES6 die Destrukturierung nicht unterstützt. Schlüssel sind nicht nur Zeichenfolgen in ES6 Map und Python Dict. Auch wenn ich den "Zupf" -Stil der Objektzerstörung in ES6 liebe, ist der Zuweisungsstil nicht einfach. Was ist hier los? let {a: waffles} = params. Es dauert einige Sekunden, um es herauszufinden, selbst wenn Sie daran gewöhnt sind.
John Christopher Jones
1
@ naught101 Situativ nützlich mit bösen Überraschungen für Kompromisse. Für Benutzer: In Python kann jedes Objekt seine eigenen str / repr-Methoden bereitstellen. Es könnte sogar verlockend sein, dies für leicht komplexe Schlüsselobjekte (z. B. benannte Tupel) zu tun, um die JSON-Serialisierung zu vereinfachen. Jetzt kratzen Sie sich am Kopf, warum Sie einen Schlüssel nicht namentlich zerstören können. Warum funktioniert dies auch für Elemente, jedoch nicht für Attribute? Viele Bibliotheken bevorzugen attrs. Für Implementierer verwechselt diese ES6-Funktion Symbole (bindbare Namen) und Zeichenfolgen: In JavaScript sinnvoll, aber Python hat viel umfangreichere Ideen im Spiel. Außerdem würde es einfach hässlich aussehen.
John Christopher Jones

Antworten:

10

Wenn Sie Angst vor den Problemen bei der Verwendung von den Einheimischen dictionary beteiligt sind und Sie es vorziehen , Ihre ursprüngliche Strategie, bestellten Wörterbücher aus Python 2.7 und 3.1 folgen collections.OrderedDicts ermöglicht es Ihnen , Sie Wörterbuch Elemente in der Reihenfolge wiederherzustellen , in dem sie zuerst eingeführt wurden

Joaquin
quelle
@ Stephen Sie haben Glück, weil OrderedDicts von Python 3.1
Joaquin
Ich freue mich darauf...! Dachte, das war für mich immer ein Knackpunkt für Python (die bestellten Wörterbücher wurden nicht mit den anderen Batterien geladen)
Hatmatrix
4
Derzeit sind ab Version 3.5 alle Wörterbücher bestellt. Dies ist noch nicht "garantiert", was bedeutet, dass es sich ändern könnte.
Charles Merriam
3
Es ist garantiert ab 3.6+.
Naught101
118
from operator import itemgetter

params = {'a': 1, 'b': 2}

a, b = itemgetter('a', 'b')(params)

Anstelle von ausgefeilten Lambda-Funktionen oder Wörterbuchverständnis kann auch eine eingebaute Bibliothek verwendet werden.

Zachary822
quelle
9
Dies sollte wahrscheinlich die akzeptierte Antwort sein, da dies der pythonischste Weg ist, dies zu tun. Sie können die Antwort sogar attrgetteraus demselben Standardbibliotheksmodul erweitern, das für Objektattribute ( obj.a) funktioniert . Dies ist ein wesentlicher Unterschied zu JavaScript, wo obj.a === obj["a"].
John Christopher Jones
Wenn der Schlüssel im Wörterbuch nicht vorhanden ist, KeyErrorwird eine Ausnahme
ausgelöst
2
Aber Sie geben jetzt zweimal a und b in die Destrukturierungserklärung ein
Otto
1
@JohnChristopherJones Das scheint mir nicht sehr natürlich zu sein. Die Verwendung vorhandener Inhalte bedeutet nicht, dass es verständlich ist. Ich bezweifle, dass viele Leute einen echten Code sofort verstehen würden. Auf der anderen Seite ist, wie vorgeschlagen a, b = [d[k] for k in ('a','b')], viel natürlicher / lesbarer (die Form ist viel häufiger). Dies ist immer noch eine interessante Antwort, aber das ist nicht die einfachste Lösung.
cglacet
@cglacet Ich denke, es hängt davon ab, wie oft Sie das lesen / implementieren müssen. Wenn Sie routinemäßig dieselben 3 Schlüssel auswählen, ist die get_id = itemgetter(KEYS)spätere Verwendung serial, code, ts = get_id(document)einfacher. Zugegeben, Sie müssen mit Funktionen höherer Ordnung vertraut sein, aber Python ist im Allgemeinen sehr vertraut mit ihnen. ZB siehe die Dokumente für Dekorateure wie @contextmanager.
John Christopher Jones
28

Eine Möglichkeit, dies mit weniger Wiederholungen als Jochens Vorschlag zu tun, ist eine Hilfsfunktion. Dies gibt Ihnen die Flexibilität, Ihre Variablennamen in beliebiger Reihenfolge aufzulisten und nur eine Teilmenge der im Diktat enthaltenen Elemente zu zerstören:

pluck = lambda dict, *args: (dict[arg] for arg in args)

things = {'blah': 'bleh', 'foo': 'bar'}
foo, blah = pluck(things, 'foo', 'blah')

Anstelle von Joaquins OrderedDict können Sie auch die Schlüssel sortieren und die Werte abrufen. Die einzigen Haken sind, dass Sie Ihre Variablennamen in alphabetischer Reihenfolge angeben und alles im Diktat zerstören müssen:

sorted_vals = lambda dict: (t[1] for t in sorted(dict.items()))

things = {'foo': 'bar', 'blah': 'bleh'}
blah, foo = sorted_vals(things)
ShawnFumo
quelle
4
Dies ist ein kleiner Streitpunkt, aber wenn Sie lambdaeiner Variablen eine zuweisen möchten, können Sie auch die normale Funktionssyntax mit verwenden def.
Arthur Tacca
upvoted kann man das nicht wie JS machen, wo es const {a, b} = {a: 1, b: 2} wäre
PirateApp
1
Sie haben itemgetteraus operatorder Standardbibliothek implementiert . :)
John Christopher Jones
16

Python kann nur Sequenzen "destrukturieren", keine Wörterbücher. Um zu schreiben, was Sie wollen, müssen Sie die erforderlichen Einträge einer richtigen Reihenfolge zuordnen. Für mich ist das (nicht sehr sexy) am besten passende Spiel:

a,b = [d[k] for k in ('a','b')]

Dies funktioniert auch mit Generatoren:

a,b = (d[k] for k in ('a','b'))

Hier ist ein vollständiges Beispiel:

>>> d = dict(a=1,b=2,c=3)
>>> d
{'a': 1, 'c': 3, 'b': 2}
>>> a, b = [d[k] for k in ('a','b')]
>>> a
1
>>> b
2
>>> a, b = (d[k] for k in ('a','b'))
>>> a
1
>>> b
2
Sylvain Leroux
quelle
10

Vielleicht möchten Sie wirklich so etwas tun?

def some_func(a, b):
  print a,b

params = {'a':1,'b':2}

some_func(**params) # equiv to some_func(a=1, b=2)
Jochen Ritzel
quelle
Danke, aber nicht das ... Ich destrukturiere innerhalb einer Funktion
hatmatrix
10

Hier ist eine andere Möglichkeit, dies ähnlich wie bei einer Destrukturierungszuweisung in JS zu tun :

params = {'b': 2, 'a': 1}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

Wir haben das Parameter-Wörterbuch in Schlüsselwerte entpackt (mit **) (wie in Jochens Antwort ), dann haben wir diese Werte in die Lambda-Signatur aufgenommen und sie entsprechend dem Schlüsselnamen zugewiesen - und hier ist ein Bonus - wir Holen Sie sich auch ein Wörterbuch von allem, was nicht in der Unterschrift des Lambda enthalten ist.

params = {'b': 2, 'a': 1, 'c': 3}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

Nachdem das Lambda angewendet wurde, enthält die Restvariable nun: {'c': 3}

Nützlich, um nicht benötigte Schlüssel aus einem Wörterbuch zu entfernen.

Hoffe das hilft.

O Sharv
quelle
Interessant, andererseits denke ich, dass es in einer Funktion besser wäre. Sie werden dies wahrscheinlich mehrmals verwenden und auf diese Weise haben Sie auch einen Namen darauf. (Wenn ich Funktion sage, meine ich, keine Lambda-Funktion).
cglacet
3

Versuche dies

d = {'a':'Apple', 'b':'Banana','c':'Carrot'}
a,b,c = [d[k] for k in ('a', 'b','c')]

Ergebnis:

a == 'Apple'
b == 'Banana'
c == 'Carrot'
Junaid
quelle
3

Warnung 1: Wie in den Dokumenten angegeben, funktioniert dies nicht garantiert bei allen Python-Implementierungen:

Details zur CPython-Implementierung: Diese Funktion basiert auf der Unterstützung von Python-Stack-Frames im Interpreter, die nicht in allen Implementierungen von Python garantiert ist. Wenn diese Funktion in einer Implementierung ohne Python-Stack-Frame-Unterstützung ausgeführt wird, gibt sie None zurück.

Warnung 2: Diese Funktion verkürzt den Code zwar, widerspricht jedoch wahrscheinlich der Python-Philosophie, so explizit wie möglich zu sein. Darüber hinaus werden die Probleme, auf die John Christopher Jones in den Kommentaren hingewiesen hat, nicht behandelt, obwohl Sie eine ähnliche Funktion erstellen könnten, die mit Attributen anstelle von Schlüsseln funktioniert. Dies ist nur eine Demonstration, dass Sie das tun können, wenn Sie wirklich wollen!

def destructure(dict_):
    if not isinstance(dict_, dict):
        raise TypeError(f"{dict_} is not a dict")
    # the parent frame will contain the information about
    # the current line
    parent_frame = inspect.currentframe().f_back

    # so we extract that line (by default the code context
    # only contains the current line)
    (line,) = inspect.getframeinfo(parent_frame).code_context

    # "hello, key = destructure(my_dict)"
    # -> ("hello, key ", "=", " destructure(my_dict)")
    lvalues, _equals, _rvalue = line.strip().partition("=")

    # -> ["hello", "key"]
    keys = [s.strip() for s in lvalues.split(",") if s.strip()]

    if missing := [key for key in keys if key not in dict_]:
        raise KeyError(*missing)

    for key in keys:
        yield dict_[key]
In [5]: my_dict = {"hello": "world", "123": "456", "key": "value"}                                                                                                           

In [6]: hello, key = destructure(my_dict)                                                                                                                                    

In [7]: hello                                                                                                                                                                
Out[7]: 'world'

In [8]: key                                                                                                                                                                  
Out[8]: 'value'

Mit dieser Lösung können Sie einige der Schlüssel auswählen, nicht alle, wie in JavaScript. Es ist auch sicher für vom Benutzer bereitgestellte Wörterbücher

Dekorateur-Fabrik
quelle
1

Wenn Sie diese in einer Klasse haben möchten, können Sie dies immer tun:

class AttributeDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttributeDict, self).__init__(*args, **kwargs)
        self.__dict__.update(self)

d = AttributeDict(a=1, b=2)
Abyx
quelle
Nett. Danke, aber scheint eine Möglichkeit zu sein, die Aufrufsyntax von d ['a'] in da zu ändern? Und vielleicht Methoden hinzufügen, die implizit Zugriff auf diese Parameter haben ...
Hatmatrix
0

Basierend auf der Antwort von @ShawnFumo habe ich Folgendes gefunden:

def destruct(dict): return (t[1] for t in sorted(dict.items()))

d = {'b': 'Banana', 'c': 'Carrot', 'a': 'Apple' }
a, b, c = destruct(d)

(Beachten Sie die Reihenfolge der Artikel im Diktat)

Richard
quelle
0

Da Wörterbücher garantiert ihre Einfügereihenfolge in Python> = 3.7 beibehalten , bedeutet dies, dass es heutzutage absolut sicher und idiomatisch ist, dies einfach zu tun:

params = {'a': 1, 'b': 2}
a, b = params.values()
print(a)
print(b)

Ausgabe:

1
2
Ruohola
quelle
2
Toll! Sie haben erst 9 Jahre seit meinem Posten gebraucht. :)
hatmatrix
1
Das Problem ist, dass Sie dies nicht tun können b, a = params.values(), da die Reihenfolge und nicht die Namen verwendet werden.
Corman
1
@ruohola Das ist das Problem mit dieser Lösung. Dies hängt von der Reihenfolge der Wörterbücher für Namen ab, nicht von den Namen selbst, weshalb ich dies abgelehnt habe.
Corman
1
Und das macht es zu einer schlechten Lösung, wenn es auf die Reihenfolge der Schlüssel ankommt. itemgetterist der pythonischste Weg, dies zu tun. Dies funktioniert unter Python 3.6 oder niedriger nicht und da es von der Reihenfolge abhängt, kann es zunächst verwirrend sein.
Corman
-1

Ich weiß nicht, ob es ein guter Stil ist, aber

locals().update(params)

wird den Trick machen. Sie haben dann a, bund was war in Ihrer paramsdict als lokale Variablen entspricht.

Johannes Charra
quelle
2
Bitte beachten Sie, dass dies ein großes Sicherheitsproblem sein kann, wenn das 'params'-Wörterbuch in irgendeiner Weise vom Benutzer bereitgestellt und nicht richtig gefiltert wird.
Jacek Konieczny
9
So zitieren Sie docs.python.org/library/functions.html#locals : Hinweis: Der Inhalt dieses Wörterbuchs sollte nicht geändert werden. Änderungen wirken sich möglicherweise nicht auf die Werte der vom Interpreter verwendeten lokalen und freien Variablen aus.
Jochen Ritzel
4
Die Lektion gelernt. Danke Leute.
Johannes Charra