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

4784

Ich habe zwei Python-Wörterbücher und möchte einen einzelnen Ausdruck schreiben, der diese beiden zusammengeführten Wörterbücher zurückgibt. Die update()Methode wäre genau das, was ich brauche, wenn sie ihr Ergebnis zurückgibt, anstatt ein Wörterbuch direkt zu ändern.

>>> 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 zusammengeführte Wörterbuch erhalten z, nicht x?

(Um ganz klar zu sein, dict.update()ich suche auch nach der Konfliktbewältigung, bei der der letzte gewinnt .)

Carl Meyer
quelle
1
Verwenden Sie für den Fall, dass Sie Python 3.9 Alpha verwenden, einfachz = x | y
The Daleks

Antworten:

5699

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

Für Wörterbücher xund y, zwird ein seicht aus Wörterbuch mit Werten fusionierte yvon denen ersetzen x.

  • In Python 3.5 oder höher:

    z = {**x, **y}
  • Schreiben Sie in Python 2 (oder 3.4 oder niedriger) 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 nun:

    z = merge_two_dicts(x, y)
  • In Python 3.9.0a4 oder höher (endgültiger Veröffentlichungstermin ca. Oktober 2020): PEP-584 , die hier beschrieben wurde diese weiter zu vereinfachen umgesetzt:

    z = x | y          # NOTE: 3.9+ ONLY

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) zu erhalten, in dem die Werte zusammengeführt werden und die Werte des zweiten Dikts die Werte des ersten überschreiben.

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

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

z = {**x, **y}

Und es ist in der Tat ein einziger Ausdruck.

Beachten Sie, dass wir auch mit der wörtlichen Notation verschmelzen können:

z = {**x, 'foo': 1, 'bar': 2, **y}

und nun:

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

Es wird jetzt als im Release-Zeitplan für 3.5, PEP 478 , implementiert angezeigt und hat nun den Weg in das Dokument " Was ist neu in Python 3.5" gefunden .

Da sich jedoch viele Organisationen noch in Python 2 befinden, möchten Sie dies möglicherweise abwärtskompatibel tun. Die klassische pythonische Methode, die 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 ywird es an zweiter Stelle stehen und seine Werte werden die Werte ersetzen xund somit 'b'auf 3unser Endergebnis verweisen .

Noch nicht in Python 3.5, möchte aber einen einzelnen Ausdruck

Wenn Sie noch nicht mit Python 3.5 arbeiten oder abwärtskompatiblen Code schreiben müssen und dies in einem einzelnen Ausdruck möchten , ist es am leistungsfähigsten, wenn Sie ihn in eine Funktion einfügen :

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 zum Zusammenführen einer undefinierten Anzahl von Diktaten von Null bis zu einer sehr großen Anzahl erstellen:

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 funktioniert in Python 2 und 3 für alle Diktate. zB gegebene Diktate aan g:

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

und Schlüsselwertpaare in ghaben Vorrang vor Diktaten aan fund so weiter.

Kritik anderer Antworten

Verwenden Sie nicht das, was Sie in der zuvor akzeptierten Antwort sehen:

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

In Python 2 erstellen Sie zwei Listen im Speicher für jedes Diktat, 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 schlägt dies fehl, da Sie zwei dict_itemsObjekte und nicht zwei Listen hinzufügen.

>>> 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 schlägt die Vereinigung von items()in Python 3 ( viewitems()in Python 2.7) fehl, wenn Werte nicht zerlegbare Objekte sind (wie z. B. Listen). Selbst wenn Ihre Werte hashbar sind, ist das Verhalten in Bezug auf die Priorität undefiniert , da Mengen semantisch ungeordnet sind. Also mach das nicht:

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

Dieses Beispiel zeigt, was passiert, wenn Werte nicht verwertbar 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 der Wert von x aufgrund der willkürlichen Reihenfolge der Mengen beibehalten wird:

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

Ein weiterer Hack, den Sie nicht verwenden sollten:

z = dict(x, **y)

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

Hier ist ein Beispiel für die Verwendung in Django .

Dicts sollen Hash-Schlüssel (z. B. Frozensets oder Tupel) verwenden. Diese Methode schlägt jedoch in Python 3 fehl, wenn 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

Aus der Mailingliste schrieb Guido van Rossum, der Schöpfer der Sprache:

Ich kann das Diktat ({}, ** {1: 3}) für illegal erklären, da es sich schließlich um einen Missbrauch des ** Mechanismus handelt.

und

Anscheinend wird dict (x, ** y) als "cooler Hack" für "x.update (y) aufrufen und x zurückgeben" verwendet. Persönlich finde ich es eher verabscheuungswürdig als cool.

Es ist mein Verständnis (sowie das Verständnis des Schöpfers der Sprache ), dass die beabsichtigte Verwendung darin dict(**y)besteht, Diktate zu Lesbarkeitszwecken zu erstellen, z.

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

anstatt

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

Antwort auf Kommentare

Trotz allem, was Guido sagt, entspricht dict(x, **y)es der Diktatspezifikation, die übrigens. funktioniert sowohl für Python 2 als auch für 3. Die Tatsache, dass dies nur für Zeichenfolgenschlüssel funktioniert, ist eine direkte Folge der Funktionsweise von Schlüsselwortparametern und kein kurzes Diktieren. Die Verwendung des Operators ** an dieser Stelle stellt auch keinen Missbrauch des Mechanismus dar. Tatsächlich wurde ** genau darauf ausgelegt, Diktate als Schlüsselwörter zu übergeben.

Auch hier funktioniert es nicht für 3, wenn Schlüssel keine Zeichenfolgen sind. Der implizite Aufrufvertrag sieht vor, dass Namespaces normale Diktate annehmen, während Benutzer nur Schlüsselwortargumente übergeben müssen, die Zeichenfolgen sind. Alle anderen Callables haben es erzwungen. dicthat diese Konsistenz in Python 2 gebrochen:

>>> 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 angesichts anderer Implementierungen von Python (Pypy, Jython, IronPython) schlecht. Daher wurde es in Python 3 behoben, da diese Verwendung eine bahnbrechende Änderung sein könnte.

Ich sage 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 Einschränkungen funktioniert.

Mehr Kommentare:

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

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

{**x, **y}scheint nicht mit verschachtelten Wörterbüchern umzugehen. Der Inhalt verschachtelter Schlüssel wird einfach überschrieben und nicht zusammengeführt. [...] Ich wurde von diesen Antworten verbrannt, die nicht rekursiv zusammengeführt werden, und ich war überrascht, dass niemand dies erwähnte. In meiner Interpretation des Wortes "Zusammenführen" beschreiben diese Antworten "Aktualisieren eines Diktats mit einem anderen" und nicht Zusammenführen.

Ja. Ich muss Sie auf die Frage zurückführen, die eine flache Zusammenführung von zwei Wörterbüchern erfordert , wobei die Werte des ersten durch die Werte des zweiten überschrieben werden - in einem einzigen Ausdruck.

Wenn Sie zwei Wörterbücher mit Wörterbüchern annehmen, werden diese möglicherweise rekursiv in einer einzigen Funktion zusammengeführt. Sie sollten jedoch darauf achten, die Wörterbücher aus beiden Quellen nicht zu ändern. Der sicherste Weg, dies zu vermeiden, besteht darin, beim Zuweisen von Werten eine Kopie zu erstellen. Da Schlüssel hashbar sein müssen und daher normalerweise unveränderlich sind, ist es sinnlos, sie zu kopieren:

from copy import deepcopy

def dict_of_dicts_merge(x, y):
    z = {}
    overlapping_keys = x.keys() & y.keys()
    for key in overlapping_keys:
        z[key] = dict_of_dicts_merge(x[key], y[key])
    for key in x.keys() - overlapping_keys:
        z[key] = deepcopy(x[key])
    for key in y.keys() - overlapping_keys:
        z[key] = deepcopy(y[key])
    return z

Verwendungszweck:

>>> x = {'a':{1:{}}, 'b': {2:{}}}
>>> y = {'b':{10:{}}, 'c': {11:{}}}
>>> dict_of_dicts_merge(x, y)
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}

Eventualverbindlichkeiten für andere Werttypen zu finden, geht weit über den Rahmen dieser Frage hinaus, daher werde ich Sie auf meine Antwort auf die kanonische Frage zu einem "Zusammenführen von Wörterbüchern der Wörterbücher" hinweisen .

Weniger leistungsfähig, aber korrekte Ad-hocs

Diese Ansätze sind weniger performant, liefern jedoch ein korrektes Verhalten. Sie werden viel weniger performant als copyund updateoder das neue auspacken , weil sie durchlaufen jedes Schlüssel-Wert - Paar auf einer höheren Abstraktionsebene, aber sie tun respektieren die Rangordnung (letztere dicts haben 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 schon in 2.4, als Generatorausdrücke eingeführt wurden):

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

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

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

Performance-Analyse

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 zu Wörterbüchern

Aaron Hall
quelle
9
@MohammadAzim "Nur Zeichenfolgen" gilt nur für die Erweiterung von Schlüsselwortargumenten in aufrufbaren Dateien, nicht für die allgemeine Entpackungssyntax. Um zu demonstrieren, dass dies funktioniert: {**{(0, 1):2}}->{(0, 1): 2}
Aaron Hall
37
kurze Antworten wie z = {**x, **y}wirklich stimulieren mich
pcko1
1
Dies kann geändert werden, wenn PEP-0584 akzeptiert wird. Ein neuer Gewerkschaftsoperator wird mit der folgenden Syntax implementiert:x | y
Callam Delaney
2
Wenn eine Antwort oben eine Zusammenfassung benötigt, ist sie zu lang.
Gringo Suave
2
Hallo, oben ist eine Zusammenfassung, ja. Wie du willst. Das Ganze wäre ein großartiger Blog-Beitrag. Hinweis Py 3.4 und niedriger sind EOL, 3.5 nähert sich EOL im Zeitraum 2020-09.
Gringo Suave
1617

In Ihrem Fall können Sie Folgendes tun:

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

Dadurch wird, wie Sie es möchten, das endgültige Diktat zeingegeben und der Wert für den Schlüssel bwird ordnungsgemäß durch den yWert des zweiten ( ) Diktats überschrieben :

>>> 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. So erstellen Sie z:

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

Wenn Sie Python Version 3.9.0a4 oder höher verwenden, können Sie direkt Folgendes verwenden:

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = x | y
print(z)

Output: {'a': 1, 'c': 11, 'b': 10}
Thomas Vander Stichele
quelle
2
Verwenden Sie dies nicht, da es sehr ineffizient ist. (Siehe die folgenden Timeit-Ergebnisse.) In den Py2-Tagen war möglicherweise eine Wrapper-Funktion erforderlich, aber diese Tage sind nun vorbei.
Gringo Suave
633

Eine Alternative:

z = x.copy()
z.update(y)
Matthew Schinckel
quelle
83
Um zu verdeutlichen, warum dies nicht den Kriterien der Frage entspricht: Es ist kein einzelner Ausdruck und es wird nicht z zurückgegeben.
Alex
2
@neuronet Jeder Oneliner verschiebt normalerweise nur Code, der in eine andere Komponente kommen muss, und löst ihn dort. Dies ist definitiv einer der Fälle. Aber andere Sprachen haben dafür schönere Konstrukte als Python. und eine referenziell transparente Variante zu haben, die ihr Element zurückgibt, ist eine schöne Sache.
Alex
12
Sagen Sie es so: Wenn Sie zwei Zeilen mit Kommentaren einfügen müssen, in denen Sie den Personen, an die Sie Ihren Code übergeben, Ihre eine Codezeile erklären ... haben Sie es wirklich in einer Zeile getan? :) Ich stimme voll und ganz zu, dass Python dafür nicht gut ist: Es sollte einen viel einfacheren Weg geben. Während diese Antwort eher pythonisch ist, ist sie wirklich so explizit oder klar? Updateist nicht eine der "Kern" -Funktionen, die Menschen häufig nutzen.
Eric
Nun, wenn die Leute darauf bestehen, es zu einem Oneliner zu machen, können Sie immer (lambda z: z.update(y) or z)(x.copy())
Folgendes
340

Eine weitere, prägnantere Option:

z = dict(x, **y)

Hinweis : Dies ist eine beliebte Antwort geworden. Es ist jedoch wichtig darauf hinzuweisen, dass ydie Tatsache, dass dies überhaupt funktioniert, ein Missbrauch eines CPython-Implementierungsdetails ist und in Python 3 nicht funktioniert. oder in PyPy, IronPython oder Jython. Auch Guido ist kein Fan . Daher kann ich diese Technik nicht für vorwärtskompatiblen oder implementierungsübergreifenden tragbaren Code empfehlen, was wirklich bedeutet, dass sie vollständig vermieden werden sollte.

Carl Meyer
quelle
Funktioniert gut in Python 3 und PyPy und PyPy 3 , kann nicht mit Jython oder Iron sprechen. Angesichts der Tatsache, dass dieses Muster explizit dokumentiert ist (siehe das dritte Konstruktorformular in dieser Dokumentation), würde ich behaupten, dass es sich nicht um ein "Implementierungsdetail" handelt, sondern um eine absichtliche Verwendung von Funktionen.
Amcgregor
5
@amcgregor Sie haben die Schlüsselphrase "Wenn y Schlüssel ohne Zeichenfolge hat" verpasst. Das funktioniert in Python3 nicht. Die Tatsache, dass es in CPython 2 funktioniert, ist ein Implementierungsdetail, auf das man sich nicht verlassen kann. Wenn alle Ihre Schlüssel garantiert Zeichenfolgen sind, ist dies eine vollständig unterstützte Option.
Carl Meyer
214

Dies wird wahrscheinlich keine beliebte Antwort sein, aber Sie möchten dies mit ziemlicher Sicherheit nicht tun. Wenn Sie eine Kopie wünschen, die zusammengeführt werden soll, verwenden Sie copy (oder deepcopy , je nachdem, was Sie möchten) und aktualisieren Sie dann. Die beiden Codezeilen sind viel besser lesbar - pythonischer - als die Erstellung einzelner Zeilen mit .items () + .items (). Explizit ist besser als implizit.

Wenn Sie .items () (vor Python 3.0) verwenden, erstellen Sie außerdem eine neue Liste, die die Elemente aus dem Diktat enthält. Wenn Ihre Wörterbücher groß sind, ist das ziemlich viel Aufwand (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.

In Bezug auf die 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 lohnt sich die winzige Verlangsamung zwischen den ersten beiden 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.

Tony Meyer
quelle
150

In einer nachfolgenden Antwort haben Sie nach der relativen Leistung dieser beiden Alternativen gefragt:

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

Zumindest auf meinem Computer (einem ziemlich normalen x86_64 mit Python 2.5.2) ist die Alternative z2nicht nur kürzer und einfacher, sondern auch erheblich schneller. Sie können dies selbst überprüfen, indem Sie das timeitmit Python gelieferte Modul verwenden.

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

z2gewinnt um den Faktor 3,5 oder so. Verschiedene Wörterbücher scheinen ganz unterschiedliche Ergebnisse zu liefern, scheinen aber z2immer die Nase vorn zu haben. (Wenn Sie für denselben Test inkonsistente Ergebnisse erhalten , versuchen Sie, -reine Zahl einzugeben, die größer als die Standardzahl 3 ist.)

Beispiel 2: Nicht überlappende Wörterbücher, die 252 kurze Zeichenfolgen Ganzzahlen zuordnen und umgekehrt:

% 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 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 auf den Aufwand beim Erstellen der beiden Artikellisten zurückzuführen ist, was mich wiederum zu der Frage veranlasste, ob diese Variante möglicherweise besser funktioniert:

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

Ein paar schnelle Tests, z

% 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

Lassen Sie mich zu dem Schluss kommen, dass dies z3etwas schneller ist als z1, aber bei weitem nicht so schnell wie z2. Auf keinen Fall die zusätzliche Eingabe wert.

In dieser Diskussion fehlt noch etwas Wichtiges, nämlich ein Leistungsvergleich dieser Alternativen mit der "offensichtlichen" Art, zwei Listen zusammenzuführen: Verwenden der updateMethode. Um zu versuchen, die Dinge mit den Ausdrücken gleichzusetzen, von denen keiner x oder y ändert, werde ich eine Kopie von x erstellen, anstatt sie an Ort und Stelle zu ändern, 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, z0und z2scheinen im Wesentlichen identische Leistung zu haben. Denken Sie, dass dies ein Zufall sein könnte? Ich nicht....

Tatsächlich würde ich sogar behaupten, dass es für reinen Python-Code unmöglich ist, etwas Besseres zu tun. Und wenn Sie in einem C-Erweiterungsmodul deutlich besser abschneiden können, sind die Python-Leute möglicherweise daran interessiert, Ihren Code (oder eine Variation Ihres Ansatzes) in den Python-Kern zu integrieren. Python wird dictan vielen Orten verwendet. Die Optimierung des Betriebs ist eine große Sache.

Sie können dies auch als schreiben

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

wie Tony, aber (nicht überraschend) hat der Unterschied in der Notation keinen messbaren Einfluss auf die Leistung. Verwenden Sie, was für Sie richtig aussieht. Natürlich hat er absolut Recht darauf hinzuweisen, dass die Version mit zwei Aussagen viel einfacher zu verstehen ist.

Zaphod
quelle
5
Dies funktioniert in Python 3 nicht. items()ist nicht verkettbar und iteritemsexistiert nicht.
Antti Haapala
127

In Python 3.0 und höher können Sie verwenden, collections.ChainMapwelche Gruppen mehrere Diktate oder andere Zuordnungen zusammenfassen, um eine einzelne, aktualisierbare Ansicht zu erstellen:

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

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

Update für Python 3.5 und höher : Sie können PEP 448 Extended Dictionary Packen und Entpacken verwenden. Das geht schnell und einfach:

>>> x = {'a':1, 'b': 2}
>>> y = y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}
Raymond Hettinger
quelle
3
Bei der Verwendung von ChainMap ist jedoch Vorsicht geboten. Wenn Sie doppelte Schlüssel haben, werden die Werte aus der ersten Zuordnung verwendet. Wenn Sie ein delon aufrufen, löscht eine ChainMap c die erste Zuordnung dieses Schlüssels.
Jägerin
7
@Prerit Was würden Sie sonst noch erwarten? So funktionieren verkettete Namespaces. Überlegen Sie, wie $ PATH in Bash funktioniert. Das Löschen einer ausführbaren Datei im Pfad schließt eine weitere ausführbare Datei mit demselben Namen weiter stromaufwärts nicht aus.
Raymond Hettinger
2
@ Raymond Hettinger Ich stimme zu, habe nur eine Warnung hinzugefügt. Die meisten Leute wissen vielleicht nichts davon. : D
Jägerin
@Prerit Sie könnten besetzen, um dies dictzu vermeiden, dh:dict(ChainMap({}, y, x))
wjandrea
113

Ich wollte etwas Ähnliches, aber mit der Möglichkeit anzugeben, wie die Werte auf doppelten Schlüsseln zusammengeführt wurden, habe ich dies gehackt (aber nicht intensiv 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
rcreswick
quelle
88

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 die Änderungen.

Stan
quelle
1
Dies beantwortet die Frage nicht. In der Frage wird eindeutig nach einem neuen Wörterbuch z aus den ursprünglichen Wörterbüchern x und y gefragt, wobei die Werte von y die von x ersetzen - kein aktualisiertes Wörterbuch. Diese Antwort ändert y an Ort und Stelle, indem Werte aus x hinzugefügt werden. Schlimmer noch, diese Werte werden nicht kopiert, sodass das geänderte Wörterbuch y weiter geändert werden kann und Änderungen im Wörterbuch x wiedergegeben werden können. @ Jérôme Ich hoffe, dieser Code verursacht keine Fehler für Ihre Anwendung - zumindest sollten Sie Deepcopy verwenden, um die Werte zu kopieren.
Aaron Hall
1
@ AaronHall stimmte zu, dass dies die Frage nicht beantwortet. Aber es entspricht meinem Bedürfnis. Ich verstehe diese Einschränkungen, aber das ist in meinem Fall kein Problem. Wenn man daran denkt, ist der Name vielleicht irreführend, da er eine Deepcopy hervorrufen könnte, die er nicht bietet. Aber es befasst sich mit tiefer Verschachtelung. Hier ist eine weitere Implementierung aus dem Martellibot: stackoverflow.com/questions/3232943/… .
Jérôme
72

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 auf CPython. Diese Version funktioniert auch in Python 3 , wenn Sie ändern iteritems()zu items(), die durch das 2to3 - Tool automatisch erfolgt.

Persönlich gefällt mir 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 ist, dass Werte von y Vorrang vor Werten von x haben, aber ich glaube nicht, dass es schwierig ist, das herauszufinden.

Driax
quelle
71

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}

In Python 3.9 verwenden Sie auch | und | = mit dem folgenden Beispiel aus PEP 584

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
Bilal Syed Hussain
quelle
Inwiefern ist diese Lösung besser als die dict(x, **y)Lösung? Wie Sie (@CarlMeyer) in der Notiz Ihrer eigenen Antwort ( stackoverflow.com/a/39858/2798610 ) erwähnt haben, hält Guido diese Lösung für illegal .
Blackeagle52
14
Guido mag es dict(x, **y)aus dem (sehr guten) Grund nicht, dass es ynur Schlüssel enthält, die gültige Schlüsselwortargumentnamen sind (es sei denn, Sie verwenden CPython 2.7, wo der Diktatkonstruktor betrügt). Dieser Einwand / diese Einschränkung gilt nicht für PEP 448, das die **Entpackungssyntax verallgemeinert, um Literale zu diktieren. Diese Lösung hat also die gleiche Präzision wie dict(x, **y)ohne den Nachteil.
Carl Meyer
62
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

Bei Elementen mit Schlüsseln in beiden Wörterbüchern ('b') können Sie steuern, welches in der Ausgabe landet, indem Sie das letzte setzen.

Greg Hewgill
quelle
In Python 3 erhalten Sie TypeError: nicht unterstützte Operandentypen für +: 'dict_items' und 'dict_items' ... Sie sollten jedes Diktat mit list () kapseln, wie: dict (list (x.items ()) + list (y.items ()))
justSaid
49

Obwohl die Frage bereits mehrmals beantwortet wurde, wurde diese einfache Lösung des Problems noch nicht aufgeführt.

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z4 = {}
z4.update(x)
z4.update(y)

Es ist so schnell wie z0 und das böse z2, aber leicht zu verstehen und zu ändern.

Phobie
quelle
3
Aber es sind eher drei Aussagen als ein Ausdruck
fortran
14
Ja! Die erwähnten Ein-Ausdruck-Lösungen sind entweder langsam oder böse. Guter Code ist lesbar und wartbar. Das Problem ist also die Frage, nicht die Antwort. Wir sollten nach der besten Lösung eines Problems fragen, nicht nach einer einzeiligen Lösung.
Phobie
7
Verlieren Sie das z4 = {}und ändern Sie die nächste Zeile in z4 = x.copy()- besser als nur guter Code macht keine unnötigen Dinge (was ihn noch lesbarer und wartbarer macht).
Martineau
3
Ihr Vorschlag würde dies in Matthews Antwort ändern. Obwohl seine Antwort in Ordnung ist, denke ich, dass meine besser lesbar und besser zu warten ist. Die zusätzliche Zeile wäre nur dann schlecht, wenn sie Ausführungszeit kosten würde.
Phobie
47
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

Unter diesen zwielichtigen und zweifelhaften Antworten ist dieses leuchtende Beispiel der einzige gute Weg, um Diktate in Python zusammenzuführen, die vom lebenslangen Diktator Guido van Rossum selbst gebilligt wurden ! Jemand anderes schlug die Hälfte davon vor, setzte sie jedoch nicht in eine Funktion ein.

print dict_merge(
      {'color':'red', 'model':'Mini'},
      {'model':'Ferrari', 'owner':'Carl'})

gibt:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}
Sam Watkins
quelle
39

Wenn Sie denken, Lambdas sind böse, dann lesen Sie nicht weiter. Wie gewünscht können Sie die schnelle und speichereffiziente Lösung mit einem Ausdruck schreiben:

x = {'a':1, 'b':2}
y = {'b':10, 'c':11}
z = (lambda a, b: (lambda a_copy: a_copy.update(b) or a_copy)(a.copy()))(x, y)
print z
{'a': 1, 'c': 11, 'b': 10}
print x
{'a': 1, 'b': 2}

Wie oben vorgeschlagen, ist die Verwendung von zwei Zeilen oder das Schreiben einer Funktion wahrscheinlich der bessere Weg.

EMS
quelle
33

Sei pythonisch. Verwenden Sie ein Verständnis :

z={i:d[i] for d in [x,y] for i in d}

>>> print z
{'a': 1, 'c': 11, 'b': 10}
Robino
quelle
1
Als Funktion:def dictmerge(*args): return {i:d[i] for d in args for i in d}
Jessexknight
1
Speichern Sie eine Suche, indem Sie die Schlüssel / Wert-Paare direkt iterieren:z={k: v for d in (x, y) for k, v in d.items()}
ShadowRanger
30

In Python3 gibt die itemsMethode keine Liste mehr zurück , sondern eine Ansicht , die sich wie eine Menge verhält . In diesem Fall müssen Sie die festgelegte Union verwenden, da die Verkettung mit +nicht funktioniert:

dict(x.items() | y.items())

Für Python3-ähnliches Verhalten in Version 2.7 sollte die viewitemsMethode anstelle von items:

dict(x.viewitems() | y.viewitems())

Ich bevorzuge diese Notation sowieso, da es natürlicher erscheint, sie als eine festgelegte Gewerkschaftsoperation zu betrachten, anstatt sie zu verketten (wie der Titel zeigt).

Bearbeiten:

Noch ein paar Punkte für Python 3. Beachten Sie zunächst, dass der dict(x, **y)Trick in Python 3 nur funktioniert, wenn die Schlüssel yZeichenfolgen sind.

Raymond Hettinger des Chainmap Auch Antwort ist ziemlich elegant, da es eine beliebige Anzahl von dicts als Argumente, aber aus der Dokumentation sieht es aus wie es aussieht , nacheinander durch eine Liste aller dicts für jeden Nachschlag:

Lookups durchsuchen die zugrunde liegenden Zuordnungen nacheinander, bis ein Schlüssel gefunden wurde.

Dies kann Sie verlangsamen, wenn Ihre Anwendung viele Suchvorgänge enthält:

In [1]: from collections import ChainMap
In [2]: from string import ascii_uppercase as up, ascii_lowercase as lo; x = dict(zip(lo, up)); y = dict(zip(up, lo))
In [3]: chainmap_dict = ChainMap(y, x)
In [4]: union_dict = dict(x.items() | y.items())
In [5]: timeit for k in union_dict: union_dict[k]
100000 loops, best of 3: 2.15 µs per loop
In [6]: timeit for k in chainmap_dict: chainmap_dict[k]
10000 loops, best of 3: 27.1 µs per loop

Also um eine Größenordnung langsamer für Lookups. Ich bin ein Fan von Chainmap, sehe aber weniger praktisch aus, wenn es viele Suchvorgänge gibt.

Bart
quelle
22

Missbrauch führt zu einer Lösung mit einem Ausdruck für Matthews Antwort :

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (lambda f=x.copy(): (f.update(y), f)[1])()
>>> z
{'a': 1, 'c': 11, 'b': 10}

Sie sagten, Sie wollten einen Ausdruck, also habe ich missbraucht lambda, um einen Namen zu binden, und Tupel, um Lambdas Ein-Ausdruck-Limit zu überschreiben. Fühlen Sie sich frei zu erschaudern.

Sie können dies natürlich auch tun, wenn Sie es nicht kopieren möchten:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (x.update(y), x)[1]
>>> z
{'a': 1, 'b': 10, 'c': 11}
Claudiu
quelle
22

Einfache Lösung mit itertools, die die Ordnung bewahrt (letztere haben Vorrang)

import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, args)))

Und es ist Verwendung:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> merge(x, y)
{'a': 1, 'b': 10, 'c': 11}

>>> z = {'c': 3, 'd': 4}
>>> merge(x, y, z)
{'a': 1, 'b': 10, 'c': 3, 'd': 4}
reubano
quelle
22

Zwei Wörterbücher

def union2(dict1, dict2):
    return dict(list(dict1.items()) + list(dict2.items()))

n Wörterbücher

def union(*dicts):
    return dict(itertools.chain.from_iterable(dct.items() for dct in dicts))

sumhat schlechte Leistung. Siehe https://mathieularose.com/how-not-to-flatten-a-list-of-lists-in-python/

Mathieu Larose
quelle
16

Obwohl die Antworten für dieses flache Wörterbuch gut waren, führt keine der hier definierten Methoden tatsächlich eine tiefe Wörterbuchzusammenführung durch.

Beispiele folgen:

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

Man würde ein Ergebnis von so etwas erwarten:

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

Stattdessen erhalten wir Folgendes:

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

Der 'one'-Eintrag sollte' depth_2 'und' extra 'als Elemente in seinem Wörterbuch haben, wenn es sich wirklich um eine Zusammenführung handelt.

Die Verwendung der Kette funktioniert auch nicht:

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

Ergebnisse in:

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

Die tiefe Verschmelzung, die rcwesick gab, führt auch zum gleichen Ergebnis.

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

Thanh Lim
quelle
11

(Nur für Python2.7 *; es gibt einfachere Lösungen für Python3 *.)

Wenn Sie dem Importieren eines Standardbibliotheksmoduls nicht abgeneigt sind, können Sie dies tun

from functools import reduce

def merge_dicts(*dicts):
    return reduce(lambda a, d: a.update(d) or a, dicts, {})

(Das or aBit in der lambdaist notwendig, weil dict.updateimmer Noneauf Erfolg zurückkehrt.)

kjo
quelle
11

Wenn es Ihnen nichts ausmacht, zu mutieren x,

x.update(y) or x

Einfach, lesbar, performant. Sie wissen, dass update() immer zurückgegeben wird None, was ein falscher Wert ist. Der obige Ausdruck wird also xnach der Aktualisierung immer ausgewertet .

Mutationsmethoden in der Standardbibliothek (wie .update()) werden gemäß NoneKonvention zurückgegeben, sodass dieses Muster auch für diese funktioniert. Wenn Sie eine Methode verwenden, die dieser Konvention ornicht entspricht, funktioniert sie möglicherweise nicht. Sie können jedoch stattdessen eine Tupelanzeige und einen Index verwenden, um daraus einen einzelnen Ausdruck zu machen. Dies funktioniert unabhängig davon, wie das erste Element ausgewertet wird.

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

Wenn Sie noch keine xVariable haben, können Sie lambdaeine lokale Variable erstellen, ohne eine Zuweisungsanweisung zu verwenden. Dies läuft darauf hinaus, lambdaals let-Ausdruck zu verwenden , was eine gängige Technik in funktionalen Sprachen ist, aber möglicherweise unpythonisch.

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

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

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

Wenn Sie eine Kopie wünschen, ist der PEP 448-Stil am einfachsten {**x, **y}. Wenn dies in Ihrer (älteren) Python-Version nicht verfügbar ist , funktioniert das Let- Muster auch hier.

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

(Das entspricht natürlich, (z := x.copy()).update(y) or zaber wenn Ihre Python-Version dafür neu genug ist, ist der PEP 448-Stil verfügbar.)

Gilch
quelle
10

Ich habe hier und anderswo auf Ideen zurückgegriffen und 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.

Bijou Trouvaille
quelle
10

Das Problem, das ich mit den bisher aufgeführten Lösungen habe, ist, dass im zusammengeführten Wörterbuch der Wert für Schlüssel "b" 10 ist, aber meiner Meinung nach sollte er 12 sein. In diesem Licht präsentiere ich Folgendes:

import timeit

n=100000
su = """
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
"""

def timeMerge(f,su,niter):
    print "{:4f} sec for: {:30s}".format(timeit.Timer(f,setup=su).timeit(n),f)

timeMerge("dict(x, **y)",su,n)
timeMerge("x.update(y)",su,n)
timeMerge("dict(x.items() + y.items())",su,n)
timeMerge("for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k] ",su,n)

#confirm for loop adds b entries together
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]
print "confirm b elements are added:",x

Ergebnisse:

0.049465 sec for: dict(x, **y)
0.033729 sec for: x.update(y)                   
0.150380 sec for: dict(x.items() + y.items())   
0.083120 sec for: for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]

confirm b elements are added: {'a': 1, 'c': 11, 'b': 12}
upandacross
quelle
1
Sie könnten interessiert sein an cytoolz.merge_with( toolz.readthedocs.io/en/latest/… )
bli
10

Es ist so dumm, dass .updatenichts zurückgibt.
Ich benutze nur eine einfache Hilfsfunktion, um das Problem zu lösen:

def merge(dict1,*dicts):
    for dict2 in dicts:
        dict1.update(dict2)
    return dict1

Beispiele:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy
GetFree
quelle
10
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

Dies sollte Ihr Problem lösen.

reetesh11
quelle
9

Dies kann mit einem einzigen Diktatverständnis erfolgen:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> { key: y[key] if key in y else x[key]
      for key in set(x) + set(y)
    }

Meiner Ansicht nach die beste Antwort für den Teil "Einzelausdruck", da keine zusätzlichen Funktionen benötigt werden und er kurz ist.

RemcoGerlich
quelle
Ich vermute jedoch, dass die Leistung nicht sehr gut sein wird. Das Erstellen eines Satzes aus jedem Diktat und das anschließende Durchlaufen der Tasten bedeutet jedes Mal eine erneute Suche nach dem Wert (obwohl relativ schnell, erhöht sich dennoch die Reihenfolge der Skalierungsfunktion)
Breezer,
2
Alles hängt von der Version der Python ab, die wir verwenden. In 3.5 und höher gibt {** x, ** y} das verkettete Wörterbuch
Rashid Mv
9

Dank PEP 572: Assignment Expressions wird es eine neue Option geben, wenn Python 3.8 veröffentlicht wird ( geplant für den 20. Oktober 2019 ) . Mit dem neuen Operator für Zuweisungsausdrücke können Sie das Ergebnis von zuweisen und es dennoch zum Aufrufen verwenden. Dabei bleibt der kombinierte Code ein einzelner Ausdruck anstelle von zwei Anweisungen, die sich ändern::=copyupdate

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

zu:

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

während sie sich in jeder Hinsicht identisch verhalten. Wenn Sie auch das Ergebnis zurückgeben müssen dict(Sie haben nach einem Ausdruck gefragt dict, der das zurückgibt; das oben Gesagte erstellt und weist es zu newdict, gibt es aber nicht zurück, sodass Sie es nicht verwenden können, um ein Argument wie es ist a la an eine Funktion zu übergeben myfunc((newdict := dict1.copy()).update(dict2))) , dann fügen sie einfach or newdictbis zum Ende (da updatekehrt None, die falsy ist, wird es dann bewerten und Rückkehr newdictals Ergebnis des Ausdrucks):

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

Wichtiger Vorbehalt: Im Allgemeinen würde ich diesen Ansatz zugunsten von:

newdict = {**dict1, **dict2}

Der Entpackungsansatz ist klarer (für alle, die sich mit allgemeinem Entpacken auskennen, was Sie sollten ), erfordert überhaupt keinen Namen für das Ergebnis (daher ist es viel prägnanter, wenn ein temporäres Element erstellt wird, das sofort an a übergeben wird Funktion oder in einem list/ tupleLiteral oder ähnlichem enthalten) und ist mit ziemlicher Sicherheit auch schneller, da es (auf CPython) ungefähr gleichbedeutend ist mit:

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

Da dies jedoch auf der C-Ebene unter Verwendung der konkreten dictAPI erfolgt, ist kein Aufwand für die Suche / Bindung dynamischer Methoden oder den Versand von Funktionsaufrufen erforderlich (wobei (newdict := dict1.copy()).update(dict2)das Verhalten unvermeidlich identisch mit dem des ursprünglichen Zweiliners ist und die Arbeit in diskreten Schritten mit dynamischer Suche ausgeführt wird / Bindung / Aufruf von Methoden.

Es ist auch erweiterbarer, da das Zusammenführen von drei dictSekunden offensichtlich ist:

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

wo die Verwendung von Zuweisungsausdrücken nicht so skaliert; Das nächste, was Sie bekommen könnten, wäre:

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

oder ohne das vorübergehende Tupel von Nones, aber mit Wahrheitsprüfung jedes NoneErgebnisses:

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

entweder von denen offensichtlich viel häßlichere, und enthält ferner Ineffizienzen (entweder eine verschwendete temporäre tuplevon Nones für komma Trennung oder sinnlos Iness Testen jedes updates‘ NoneRückkehr zur orTrennung).

Der einzige wirkliche Vorteil des Ansatzes für Zuweisungsausdrücke besteht darin, dass:

  1. Sie haben generischen Code, der sowohl sets als auch dicts verarbeiten muss (beide unterstützen copyund update, sodass der Code ungefähr so ​​funktioniert, wie Sie es erwarten würden).
  2. Sie erwarten, dass Sie beliebige diktähnliche Objekte erhalten , nicht nur sich dictselbst, und müssen den Typ und die Semantik der linken Seite beibehalten (anstatt eine Ebene zu erhalten dict). Während dies myspecialdict({**speciala, **specialb})möglicherweise funktioniert, würde es eine zusätzliche temporäre Funktion beinhalten dict, und wenn myspecialdictFeatures einfach dictnicht beibehalten werden können (z. B. reguläre dicts behalten jetzt die Reihenfolge basierend auf dem ersten Erscheinungsbild eines Schlüssels und den Wert basierend auf dem letzten Erscheinungsbild eines Schlüssels bei; Wenn die Reihenfolge basierend auf dem beibehalten wird, letztenWenn ein Schlüssel so angezeigt wird, dass ein Wert aktualisiert wird, wird er auch an das Ende verschoben. Dann wäre die Semantik falsch. Da die Version des Zuweisungsausdrucks die genannten Methoden verwendet (die vermutlich überladen sind, um sich angemessen zu verhalten), wird niemals eine erstellt Methodendictüberhaupt (es dict1sei denn, es war bereits ein dict), wobei der ursprüngliche Typ (und die Semantik des ursprünglichen Typs) beibehalten wurden, wobei zeitliche Faktoren vermieden wurden.
ShadowRanger
quelle
8
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> x, z = dict(x), x.update(y) or x
>>> x
{'a': 1, 'b': 2}
>>> y
{'c': 11, 'b': 10}
>>> z
{'a': 1, 'c': 11, 'b': 10}
John La Rooy
quelle
Diese Methode überschreibt xmit ihrer Kopie. Wenn xes sich um ein Funktionsargument handelt, funktioniert dies nicht (siehe Beispiel )
bartolo-otrit