7080 Stimmen

Wie führe ich zwei Wörterbücher in einem einzigen Ausdruck zusammen (Vereinigung von Wörterbüchern)?

Ich möchte zwei Wörterbücher zu einem neuen Wörterbuch zusammenführen.

x = {'a': 1, 'b': 2}
y = {'b': 10, 'c': 11}

z = merge(x, y)

>>> z
{'a': 1, 'b': 10, 'c': 11}

使用方法 x.update(y) modifiziert x anstelle eines neuen Wörterbuchs zu erstellen. Ich möchte auch, dass die Konfliktbehandlung des letzten Gewinners von dict.update() auch.

26voto

Thanh Lim Punkte 275

Auch wenn die Antworten in diesem Fall gut waren flach Wörterbuch, keine der hier definierten Methoden führt tatsächlich eine tiefe Wörterbuchzusammenführung durch.

Es folgen Beispiele:

a = { 'one': { 'depth_2': True }, 'two': True }
b = { 'one': { 'extra': False } }
print dict(a.items() + b.items())

Man würde ein Ergebnis in dieser Art erwarten:

{ 'one': { 'extra': False', 'depth_2': True }, 'two': True }

Stattdessen bekommen wir dies:

{'two': True, 'one': {'extra': False}}

Der "one"-Eintrag hätte "depth_2" und "extra" als Einträge in seinem Wörterbuch haben müssen, wenn es sich wirklich um eine Zusammenführung gehandelt hätte.

Auch die Verwendung einer Kette funktioniert nicht:

from itertools import chain
print dict(chain(a.iteritems(), b.iteritems()))

Ergebnisse in:

{'two': True, 'one': {'extra': False}}

Die von rcwesick beschriebene tiefe Verschmelzung führt ebenfalls zu demselben Ergebnis.

Ja, es wird funktionieren, die Beispielwörterbücher zusammenzuführen, aber keines von ihnen ist ein allgemeiner Mechanismus zum Zusammenführen. Ich werde dies später aktualisieren, sobald ich eine Methode schreibe, die eine echte Zusammenführung durchführt.

26voto

ShadowRanger Punkte 120358

Es wird eine neue Option geben, wenn Python 3.8 veröffentlicht wird ( geplant für 20. Oktober 2019 ), dank der PEP 572: Zuweisungsausdrücke . Der neue Operator für Zuweisungsausdrücke := ermöglicht es Ihnen, das Ergebnis der copy und verwenden Sie es dennoch zum Aufruf von update so dass der kombinierte Code ein einziger Ausdruck ist und nicht zwei Anweisungen, die sich ändern:

newdict = dict1.copy()
newdict.update(dict2)

zu:

(newdict := dict1.copy()).update(dict2)

und verhalten sich dabei in jeder Hinsicht identisch. Wenn Sie auch das Ergebnis zurückgeben müssen dict (Sie fragten nach einem Ausdruck, der die dict schafft und überträgt die oben genannten newdict , gibt es aber nicht zurück, so dass man es nicht verwenden kann, um ein Argument an eine Funktion zu übergeben, so wie es bei myfunc((newdict := dict1.copy()).update(dict2)) ), dann fügen Sie einfach or newdict bis zum Ende (da update devuelve None was fehlerhaft ist, wird es dann auswerten und zurückgeben newdict als Ergebnis des Ausdrucks):

(newdict := dict1.copy()).update(dict2) or newdict

Wichtiger Vorbehalt: Im Allgemeinen würde ich von diesem Ansatz abraten und stattdessen Folgendes empfehlen:

newdict = {**dict1, **dict2}

Der Ansatz des Auspackens ist klarer (für jeden, der das allgemeine Auspacken überhaupt kennt), die Sie ), erfordert überhaupt keinen Namen für das Ergebnis (daher ist es viel prägnanter, wenn ein temporäres Ergebnis konstruiert wird, das sofort an eine Funktion übergeben oder in eine list / tuple Literal oder ähnliches), und ist mit ziemlicher Sicherheit auch schneller, da es (auf CPython) ungefähr gleichwertig ist:

newdict = {}
newdict.update(dict1)
newdict.update(dict2)

aber in der C-Schicht, unter Verwendung des Betons dict API, so dass kein dynamischer Aufwand für Methodensuche/-bindung oder Funktionsaufrufe anfällt (wo (newdict := dict1.copy()).update(dict2) ist vom Verhalten her zwangsläufig identisch mit dem ursprünglichen Zweizeiler und führt die Arbeit in diskreten Schritten aus, mit dynamischem Nachschlagen/Binden/Aufrufen von Methoden.

Es ist auch besser erweiterbar, da die Zusammenführung von drei dict s ist offensichtlich:

 newdict = {**dict1, **dict2, **dict3}

wo die Verwendung von Zuweisungsausdrücken nicht so skalieren würde; am ehesten könnte man das so machen:

 (newdict := dict1.copy()).update(dict2), newdict.update(dict3)

oder ohne das temporäre Tupel von None s, aber mit Wahrheitsgehaltstests für jede None Ergebnis:

 (newdict := dict1.copy()).update(dict2) or newdict.update(dict3)

Beide Varianten sind natürlich viel hässlicher und bringen weitere Ineffizienzen mit sich (entweder eine vergeudete vorübergehende tuple de None s für die Komma-Trennung oder sinnlose Wahrheits-Tests der einzelnen update 's None Rückgabe für or Trennung).

Der einzige wirkliche Vorteil des Zuweisungsausdrucks tritt auf, wenn:

  1. Sie haben generischen Code, der beides behandeln muss set s und dict s (beide unterstützen copy y update so dass der Code ungefähr so funktioniert, wie man es erwarten würde)
  2. Sie erwarten, dass Sie beliebige diktatähnliche Objekte erhalten , nicht nur dict selbst, und muss den Typ und die Semantik der linken Seite beibehalten (anstatt mit einer einfachen dict ). Während myspecialdict({**speciala, **specialb}) funktionieren könnte, würde dies eine zusätzliche vorübergehende dict , und wenn myspecialdict hat einfache Merkmale dict nicht bewahren kann (z. B. reguläre dict s jetzt die Reihenfolge auf der Grundlage des ersten Auftretens eines Schlüssels und den Wert auf der Grundlage des letzten Auftretens eines Schlüssels beibehalten; Sie möchten vielleicht eine, die die Reihenfolge auf der Grundlage der zuletzt eines Schlüssels, so dass die Aktualisierung eines Wertes diesen auch an das Ende verschiebt), dann wäre die Semantik falsch. Da die Version des Zuweisungsausdrucks die benannten Methoden verwendet (die vermutlich überladen sind, um sich angemessen zu verhalten), wird niemals ein dict überhaupt nicht (es sei denn dict1 war bereits ein dict ), wobei der ursprüngliche Typ (und die Semantik des ursprünglichen Typs) beibehalten wird, während alle temporären Elemente vermieden werden.

26voto

gilch Punkte 9514

Wenn es Ihnen nichts ausmacht, zu mutieren x ,

x.update(y) or x

Einfach, lesbar, leistungsfähig. Sie wissen update() gibt immer zurück None was ein falscher Wert ist. Der obige Ausdruck wird also immer zu x nach der Aktualisierung.

Die meisten Mutationsmethoden in der Standardbibliothek (wie .update() ) Rückgabe None nach Konvention, so dass diese Art von Muster auch bei diesen funktionieren wird. Wenn Sie jedoch eine dict-Unterklasse oder eine andere Methode verwenden, die nicht dieser Konvention folgt, dann or kann den linken Operanden zurückgeben, was möglicherweise nicht das Gewünschte ist. Stattdessen können Sie ein Tupel display und index verwenden, das unabhängig davon funktioniert, was das erste Element auswertet (obwohl es nicht ganz so schön ist):

(x.update(y), x)[-1]

Wenn Sie nicht über x noch nicht in einer Variablen, können Sie lambda um eine Lokalisierung vorzunehmen, ohne eine Zuweisungsanweisung zu verwenden. Dies entspricht der Verwendung von lambda als Ausdruck zulassen was eine übliche Technik in funktionalen Sprachen ist, aber vielleicht unpythonisch ist.

(lambda x: x.update(y) or x)({'a': 1, 'b': 2})

Obwohl es sich nicht so sehr von der folgenden Verwendung des neuen Walross-Operators unterscheidet (nur Python 3.8+),

(x := {'a': 1, 'b': 2}).update(y) or x

insbesondere wenn Sie ein Standardargument verwenden:

(lambda x={'a': 1, 'b': 2}: x.update(y) or x)()

Wenn Sie eine Kopie wünschen, PEP 584 Stil x | y ist das Pythonischste auf 3.9+. Wenn Sie ältere Versionen unterstützen müssen, PEP 448 Stil {**x, **y} ist am einfachsten für 3.5+. Aber wenn das in Ihrer (noch älteren) Python-Version nicht verfügbar ist, kann die Ausdruck zulassen Muster funktioniert auch hier.

(lambda z=x.copy(): z.update(y) or z)()

(Das ist natürlich fast gleichbedeutend mit (z := x.copy()).update(y) or z aber wenn Ihre Python-Version neu genug dafür ist, dann wird der PEP 448-Stil verfügbar sein).

22voto

xjcl Punkte 8813

Neu in Python 3.9: Verwenden Sie den Vereinigungsoperator ( | ) zu verschmelzen dict s ähnlich wie set s:

>>> d = {'a': 1, 'b': 2}
>>> e = {'a': 9, 'c': 3}
>>> d | e
{'a': 9, 'b': 2, 'c': 3}

Bei übereinstimmenden Schlüsseln wird die rechts dict hat Vorrang .

Dies gilt auch für |= zur Änderung einer dict an Ort und Stelle:

>>> e |= d    # e = e | d
>>> e
{'a': 1, 'c': 3, 'b': 2}

20voto

Bijou Trouvaille Punkte 7666

Auf der Grundlage von Ideen hier und anderswo habe ich eine Funktion verstanden:

def merge(*dicts, **kv): 
      return { k:v for d in list(dicts) + [kv] for k,v in d.items() }

Verwendung (getestet in Python 3):

assert (merge({1:11,'a':'aaa'},{1:99, 'b':'bbb'},foo='bar')==\
    {1: 99, 'foo': 'bar', 'b': 'bbb', 'a': 'aaa'})

assert (merge(foo='bar')=={'foo': 'bar'})

assert (merge({1:11},{1:99},foo='bar',baz='quux')==\
    {1: 99, 'foo': 'bar', 'baz':'quux'})

assert (merge({1:11},{1:99})=={1: 99})

Sie könnten stattdessen ein Lambda verwenden.

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