Frage Wie füge ich zwei Wörterbücher in einem Ausdruck zusammen?


Ich habe zwei Python-Wörterbücher und möchte einen einzelnen Ausdruck schreiben, der diese beiden Wörterbücher zusammenführt. Das update() Methode wäre, was ich brauche, wenn es sein Ergebnis zurückgibt, anstatt ein Diktat vor Ort zu modifizieren.

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = x.update(y)
>>> print(z)
None
>>> x
{'a': 1, 'b': 10, 'c': 11}

Wie kann ich das endgültige fusionierte Diktat erhalten? znicht x?

(Um extra-klar zu sein, gewinnt die Letzte-gewinnt-Konfliktbehandlung von dict.update() ist was ich auch suche.)


3211
2017-09-02 07:44


Ursprung


Antworten:


Wie kann ich zwei Python-Wörterbücher in einem Ausdruck zusammenführen?

Für Wörterbücher x und y, z wird zu einem zusammengeführten Wörterbuch mit Werten aus y diese ersetzen von x.

  • In Python 3.5 oder höher:

    z = {**x, **y}
    w = {'foo': 'bar', 'baz': 'qux', **y}  # merge a dict with literal values
    
  • In Python 2 (oder 3.4 oder niedriger) schreibe eine Funktion:

    def merge_two_dicts(x, y):
        z = x.copy()   # start with x's keys and values
        z.update(y)    # modifies z with y's keys and values & returns None
        return z
    

    und

    z = merge_two_dicts(x, y)
    

Erläuterung

Angenommen, Sie haben zwei Diktate und möchten diese zu einem neuen Diktat zusammenführen, ohne die ursprünglichen Diktate zu ändern:

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}

Das gewünschte Ergebnis ist ein neues Wörterbuch (z) mit den Werten verschmolzen, und die Werte des zweiten dict überschreiben diejenigen von der ersten.

>>> z
{'a': 1, 'b': 3, 'c': 4}

Eine neue Syntax dafür, vorgeschlagen in PEP 448 und verfügbar ab Python 3.5ist

z = {**x, **y}

Und es ist tatsächlich ein einziger Ausdruck. Es zeigt sich nun wie in der Umsetzung implementiert Release-Zeitplan für 3,5, PEP 478und es hat jetzt seinen Weg in Was ist neu in Python 3.5? Dokument.

Da viele Organisationen jedoch weiterhin Python 2 verwenden, möchten Sie dies möglicherweise rückwärtskompatibel durchführen. Der klassische Python-Weg, der in Python 2 und Python 3.0-3.4 verfügbar ist, besteht darin, dies in zwei Schritten zu tun:

z = x.copy()
z.update(y) # which returns None since it mutates z

In beiden Ansätzen y kommt an zweiter Stelle und seine Werte werden ersetzt xWerte so 'b' wird darauf zeigen 3 in unserem Endergebnis.

Noch nicht auf Python 3.5, will aber ein einzelner Ausdruck

Wenn Sie noch nicht auf Python 3.5 sind oder rückwärtskompatiblen Code schreiben müssen, und Sie dies in a einzelner AusdruckAm leistungsfähigsten ist der korrekte Ansatz, wenn man es in eine Funktion bringt:

def merge_two_dicts(x, y):
    """Given two dicts, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

und dann hast du einen einzigen Ausdruck:

z = merge_two_dicts(x, y)

Sie können auch eine Funktion erstellen, um eine undefinierte Anzahl von Dicts von Null bis zu einer sehr großen Zahl zusammenzuführen:

def merge_dicts(*dict_args):
    """
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
    """
    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

Diese Funktion wird in Python 2 und 3 für alle Dicts funktionieren. z.B. gegeben dicts a zu g:

z = merge_dicts(a, b, c, d, e, f, g) 

und Schlüsselwertpaare in g wird Vorrang vor Diktaten haben a zu f, und so weiter.

Kritiken anderer Antworten

Benutze nicht, was du in der früher akzeptierten Antwort siehst:

z = dict(x.items() + y.items())

In Python 2 erstellen Sie für jedes Diktat zwei Listen im Speicher, erstellen eine dritte Liste im Speicher mit einer Länge, die der Länge der ersten beiden zusammen entspricht, und verwerfen dann alle drei Listen, um das Diktat zu erstellen. In Python 3 wird dies fehlschlagen weil du zwei hinzufügst dict_items Objekte zusammen, nicht zwei Listen -

>>> c = dict(a.items() + b.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items'

und Sie müssten sie explizit als Listen erstellen, z. z = dict(list(x.items()) + list(y.items())). Dies ist eine Verschwendung von Ressourcen und Rechenleistung.

In ähnlicher Weise, die Vereinigung von items()in Python 3 (viewitems() in Python 2.7) wird auch fehlschlagen, wenn Werte nicht hashbare Objekte sind (wie zum Beispiel Listen). Auch wenn deine Werte hashbar sind, Da Mengen semantisch ungeordnet sind, ist das Verhalten in Bezug auf die Priorität nicht definiert. Also tu das nicht:

>>> c = dict(a.items() | b.items())

In diesem Beispiel wird veranschaulicht, was passiert, wenn Werte nicht abspeicherbar sind:

>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Hier ist ein Beispiel, in dem y Vorrang haben sollte, aber stattdessen wird der Wert von x aufgrund der willkürlichen Reihenfolge der Mengen beibehalten:

>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}

Ein weiterer Hack, den du nicht benutzen solltest:

z = dict(x, **y)

Dies verwendet die dict Konstruktor, und ist sehr schnell und speichereffizient (sogar etwas mehr als unser zweistufiger Prozess), aber wenn Sie nicht genau wissen, was hier passiert (das zweite dict wird als Schlüsselwortargument an den dict-Konstruktor übergeben), es ist schwer zu lesen, es ist nicht die beabsichtigte Verwendung, und so ist es nicht Pythonic.

Hier ist ein Beispiel für den Verwendungszweck im Django saniert.

Dicts sind dazu bestimmt, hashbare Schlüssel (z.B. frozensets oder Tupel) zu nehmen, aber Diese Methode schlägt in Python 3 fehl, wenn die Schlüssel keine Zeichenfolgen sind.

>>> c = dict(a, **b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

Von dem MailinglisteGuido van Rossum, der Schöpfer der Sprache, schrieb:

Mir geht es gut   dict ({}, ** {1: 3}) zu deklarieren, ist illegal, schließlich ist es Missbrauch von   der Mechanismus.

und

Offenbar geht dict (x, ** y) als "cooler Hack" für "call" um   x.update (y) und return x ". Persönlich finde ich es verabscheuungswürdiger als   cool.

Es ist mein Verständnis (sowie das Verständnis der Schöpfer der Sprache) dass die beabsichtigte Verwendung für dict(**y) dient zum Erstellen von Dicts zur Lesbarkeit, z. B .:

dict(a=1, b=10, c=11)

Anstatt von

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

Antwort auf Kommentare

Trotz was Guido sagt, dict(x, **y) stimmt mit der dict-Spezifikation überein, die übrigens. funktioniert für Python 2 und 3. Die Tatsache, dass dies nur für String-Schlüssel funktioniert, ist eine direkte Folge davon, wie Schlüsselwortparameter funktionieren und nicht ein kurzes Kommen von dict. Der ** Operator an diesem Ort ist auch kein Missbrauch des Mechanismus, tatsächlich wurde ** genau dafür entworfen, Dicts als Schlüsselwörter zu übergeben.

Wiederum funktioniert es nicht für 3, wenn Schlüssel keine Zeichenfolgen sind. Der implizite Aufrufvertrag besagt, dass Namespaces normale Dicts verwenden, während Benutzer nur Schlüsselwortargumente übergeben müssen, bei denen es sich um Zeichenfolgen handelt. Alle anderen Callables erzwangen es. dict brach diese Konsistenz in Python 2:

>>> foo(**{('a', 'b'): None})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() keywords must be strings
>>> dict(**{('a', 'b'): None})
{('a', 'b'): None}

Diese Inkonsistenz war bei anderen Implementierungen von Python (Pypy, Jython, IronPython) schlecht. So wurde es in Python 3 behoben, da diese Verwendung eine bahnbrechende Änderung sein könnte.

Ich unterbreite Ihnen, dass es böswillige Inkompetenz ist, absichtlich Code zu schreiben, der nur in einer Version einer Sprache funktioniert oder der nur unter bestimmten willkürlichen Bedingungen funktioniert.

Ein weiterer Kommentar:

dict(x.items() + y.items()) ist immer noch die am besten lesbare Lösung für Python 2. Lesbarkeit zählt.

Meine Antwort: merge_two_dicts(x, y) tatsächlich scheint mir viel klarer zu sein, wenn wir uns eigentlich Sorgen um die Lesbarkeit machen. Und es ist nicht vorwärtskompatibel, da Python 2 zunehmend veraltet ist.

Weniger leistungsfähige, aber korrekte Ad-hocs

Diese Ansätze sind weniger performant, aber sie bieten ein korrektes Verhalten. Sie werden viel weniger performanter als copy und update oder das neue Entpacken, weil sie durch jedes Schlüssel-Wert-Paar auf einer höheren Abstraktionsebene iterieren, aber sie machen respektiere die Rangfolge (letzteres hat Vorrang)

Sie können die Diktate auch manuell innerhalb eines Diktatverständnisses verketten:

{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7

oder in Python 2.6 (und vielleicht so früh wie 2.4, als Generator-Ausdrücke eingeführt wurden):

dict((k, v) for d in dicts for k, v in d.items())

itertools.chain kettet die Iteratoren über die Schlüssel / Wert-Paare in der richtigen Reihenfolge:

import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))

Leistungsanalyse

Ich werde nur die Leistungsanalyse der Verwendungen durchführen, von denen bekannt ist, dass sie sich korrekt verhalten.

import timeit

Das Folgende wird unter Ubuntu 14.04 gemacht

In Python 2.7 (System Python):

>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.5726828575134277
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.163769006729126
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.iteritems(), y.iteritems()))))
1.1614501476287842
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
2.2345519065856934

In Python 3.5 (DeadSnakes PPA):

>>> min(timeit.repeat(lambda: {**x, **y}))
0.4094954460160807
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.7881555100320838
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.4525277839857154
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.items(), y.items()))))
2.3143140770262107
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
3.2069112799945287

Ressourcen für Wörterbücher


3336
2017-11-10 22:11



In Ihrem Fall können Sie Folgendes tun:

z = dict(x.items() + y.items())

Dies wird, wie Sie es wollen, das endgültige Diktat in die Tat umsetzen zund mache den Wert für den Schlüssel b richtig von der Sekunde übersteuert werden (y) dict's wert:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}

Wenn Sie Python 3 verwenden, ist es nur ein wenig komplizierter. Erschaffen z:

>>> z = dict(list(x.items()) + list(y.items()))
>>> z
{'a': 1, 'c': 11, 'b': 10}

1438
2017-09-02 07:50



Eine Alternative:

z = x.copy()
z.update(y)

546
2017-09-02 13:00



Eine weitere, prägnantere Option:

z = dict(x, **y)

Hinweis: Dies ist eine beliebte Antwort geworden, aber es ist wichtig, darauf hinzuweisen, dass wenn y hat keine Nicht-String-Schlüssel, die Tatsache, dass dies überhaupt funktioniert, ist ein Missbrauch einer CPython-Implementierung Detail, und es funktioniert nicht in Python 3 oder in PyPy, IronPython oder Jython. Ebenfalls, Guido ist kein Fan. Daher kann ich diese Technik nicht für einen vorwärtskompatiblen oder übergreifenden portablen Code empfehlen, was bedeutet, dass dies vollständig vermieden werden sollte.


272
2017-09-02 15:52



Das wird wahrscheinlich keine populäre Antwort sein, aber Sie wollen das sicher nicht tun. Wenn Sie eine Kopie erstellen möchten, die eine Zusammenführung ist, verwenden Sie Kopie (oder Tiefenkopie, je nachdem, was Sie wollen) und dann aktualisieren. Die beiden Codezeilen sind deutlich lesbarer - mehr Pythonic - als die Einzelzeilen-Erstellung mit .items () + .items (). Explizit ist besser als implizit.

Wenn Sie .items () (vor Python 3.0) verwenden, erstellen Sie außerdem eine neue Liste mit den Elementen aus dem Dict. Wenn Ihre Wörterbücher groß sind, ist das ziemlich viel Overhead (zwei große Listen, die weggeworfen werden, sobald das zusammengeführte Diktat erstellt wird). update () kann effizienter arbeiten, da es das zweite Diktat Element für Element durchlaufen kann.

Bezüglich Zeit:

>>> timeit.Timer("dict(x, **y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()\ntemp.update(y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.694622993469238
>>> timeit.Timer("dict(x.items() + y.items())", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
41.484580039978027

IMO die kleine Verlangsamung zwischen den ersten beiden ist es wert für die Lesbarkeit. Darüber hinaus wurden Schlüsselwortargumente für die Wörterbucherstellung nur in Python 2.3 hinzugefügt, während copy () und update () in älteren Versionen funktionieren.


167
2017-09-08 11:16



In einer anschließenden Antwort haben Sie nach der relativen Leistung dieser beiden Alternativen gefragt:

z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

Auf meiner Maschine, zumindest (eine ziemlich gewöhnliche x86_64 mit Python 2.5.2), Alternative z2 ist nicht nur kürzer und einfacher, sondern auch deutlich schneller. Sie können dies selbst überprüfen mit dem timeit Modul, das mit Python kommt.

Beispiel 1: identische Wörterbücher, die 20 aufeinanderfolgende Ganzzahlen auf sich selbst abbilden:

% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z1=dict(x.items() + y.items())'
100000 loops, best of 3: 5.67 usec per loop
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z2=dict(x, **y)' 
100000 loops, best of 3: 1.53 usec per loop

z2 gewinnt um den Faktor 3,5 oder so. Verschiedene Wörterbücher scheinen recht unterschiedliche Ergebnisse zu liefern, aber z2 scheint immer voraus zu kommen. (Wenn Sie inkonsistente Ergebnisse für die gleich test, versuche vorbei zu kommen -r mit einer Nummer größer als der Standard 3.)

Beispiel 2: Nicht überlappende Wörterbücher, die 252 kurze Strings auf ganze Zahlen und umgekehrt abbilden:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z1=dict(x.items() + y.items())'
1000 loops, best of 3: 260 usec per loop
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z2=dict(x, **y)'               
10000 loops, best of 3: 26.9 usec per loop

z2 gewinnt um etwa den Faktor 10. Das ist ein ziemlich großer Gewinn in meinem Buch!

Nachdem ich diese beiden verglichen hatte, fragte ich mich, ob z1Die schlechte Leistung könnte auf den Aufwand für die Erstellung der beiden Artikellisten zurückzuführen sein, was mich dazu brachte, mich zu fragen, ob diese Variante besser funktionieren könnte:

from itertools import chain
z3 = dict(chain(x.iteritems(), y.iteritems()))

Ein paar schnelle Tests, z.B.

% python -m timeit -s 'from itertools import chain; from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z3=dict(chain(x.iteritems(), y.iteritems()))'
10000 loops, best of 3: 66 usec per loop

führe mich zu dem Schluss z3 ist etwas schneller als z1, aber nicht annähernd so schnell wie z2. Definitiv nicht das extra Tippen wert.

In dieser Diskussion fehlt noch etwas Wichtiges, das ist ein Leistungsvergleich dieser Alternativen mit der "offensichtlichen" Art, zwei Listen zusammenzuführen: mit der update Methode. Um zu versuchen, die Dinge auf Augenhöhe mit den Ausdrücken zu halten, von denen keiner x oder y modifiziert, mache ich eine Kopie von x, anstatt sie direkt zu modifizieren, wie folgt:

z0 = dict(x)
z0.update(y)

Ein typisches Ergebnis:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z0=dict(x); z0.update(y)'
10000 loops, best of 3: 26.9 usec per loop

Mit anderen Worten, z0 und z2 scheinen im Wesentlichen identische Leistung zu haben. Denkst du, das könnte ein Zufall sein? Ich nicht....

Tatsächlich würde ich so weit gehen, zu behaupten, dass es für reinen Python-Code unmöglich ist, etwas Besseres zu tun. Und wenn Sie in einem C-Erweiterungsmodul wesentlich besser arbeiten können, stelle ich mir vor, dass die Python-Leute durchaus daran interessiert sein könnten, Ihren Code (oder eine Variante Ihres Ansatzes) in den Python-Core zu integrieren. Python verwendet dict in vielen Orten; Optimierung seiner Operationen ist eine große Sache.

Du könntest das auch als schreiben

z0 = x.copy()
z0.update(y)

wie Tony es tut, aber (nicht überraschend) stellt sich heraus, dass der Unterschied in der Notation keinen messbaren Effekt auf die Leistung hat. Verwenden Sie das, was zu Ihnen passt. Natürlich ist er absolut richtig, um darauf hinzuweisen, dass die Zwei-Aussage-Version viel einfacher zu verstehen ist.


116
2017-10-23 02:38



Ich wollte etwas Ähnliches, aber mit der Möglichkeit, anzugeben, wie die Werte auf doppelten Schlüsseln zusammengeführt wurden, also habe ich das gehackt (aber habe es nicht stark getestet). Offensichtlich ist dies kein einzelner Ausdruck, sondern ein einzelner Funktionsaufruf.

def merge(d1, d2, merge_fn=lambda x,y:y):
    """
    Merges two dictionaries, non-destructively, combining 
    values on duplicate keys as defined by the optional merge
    function.  The default behavior replaces the values in d1
    with corresponding values in d2.  (There is no other generally
    applicable merge strategy, but often you'll have homogeneous 
    types in your dicts, so specifying a merge technique can be 
    valuable.)

    Examples:

    >>> d1
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1)
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1, lambda x,y: x+y)
    {'a': 2, 'c': 6, 'b': 4}

    """
    result = dict(d1)
    for k,v in d2.iteritems():
        if k in result:
            result[k] = merge_fn(result[k], v)
        else:
            result[k] = v
    return result

86
2017-09-04 19:08



In Python 3 können Sie verwenden Sammlungen.ChainMap die mehrere dicts oder andere Zuordnungen gruppiert, um eine einzelne, aktualisierbare Ansicht zu erstellen:

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = ChainMap({}, y, x)
>>> for k, v in z.items():
        print(k, '-->', v)

a --> 1
b --> 10
c --> 11

73
2018-04-28 03:15



Rekursiv / tief ein Diktat aktualisieren

def deepupdate(original, update):
    """
    Recursively update a dict.
    Subdict's won't be overwritten but also updated.
    """
    for key, value in original.iteritems(): 
        if key not in update:
            update[key] = value
        elif isinstance(value, dict):
            deepupdate(value, update[key]) 
    return update

Demonstration:

pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

pluto_update = {
    'name': 'Pluutoo',
    'details': {
        'color': 'blue'
    }
}

print deepupdate(pluto_original, pluto_update)

Ausgänge:

{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

Danke rednaw für Änderungen.


61
2017-11-29 11:52



Die beste Version, die ich denken könnte, wenn ich keine Kopie verwende, wäre:

from itertools import chain
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
dict(chain(x.iteritems(), y.iteritems()))

Es ist schneller als dict(x.items() + y.items()) aber nicht so schnell wie n = copy(a); n.update(b)zumindest bei CPython. Diese Version funktioniert auch in Python 3, wenn Sie Änderungen vornehmen iteritems() zu items(), was automatisch vom 2to3-Tool erledigt wird.

Persönlich mag ich diese Version am besten, weil sie ziemlich gut beschreibt, was ich in einer einzigen funktionalen Syntax will. Das einzige kleine Problem ist, dass es nicht ganz offensichtlich macht, dass Werte von y Vorrang vor Werten von x haben, aber ich glaube nicht, dass es schwierig ist, das herauszufinden.


54
2017-10-14 18:55



Python 3.5 (PEP 448) ermöglicht eine schönere Syntaxoption:

x = {'a': 1, 'b': 1}
y = {'a': 2, 'c': 2}
final = {**x, **y} 
final
# {'a': 2, 'b': 1, 'c': 2}

Oder auch

final = {'a': 1, 'b': 1, **x, **y}

41
2018-02-26 21:27