Vraag Hoe twee woordenboeken samenvoegen in een enkele uitdrukking?


Ik heb twee Python-woordenboeken en ik wil een enkele uitdrukking schrijven die deze twee woordenboeken retourneert, samengevoegd. De update() methode zou zijn wat ik nodig heb, als het zijn resultaat terugkrijgt in plaats van een dictaat op zijn plaats te wijzigen.

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

Hoe kan ik dat laatste samengevoegde dictaat krijgen? z, niet x?

(Om extra-duidelijk te zijn, de laatste-één-wint conflict-afhandeling van dict.update() is wat ik ook zoek.)


3211
2017-09-02 07:44


oorsprong


antwoorden:


Hoe kan ik twee Python-woordenboeken samenvoegen in één uitdrukking?

Voor woordenboeken x en y, z wordt een samengevoegd woordenboek met waarden uit y die te vervangen van x.

  • In Python 3.5 of hoger,:

    z = {**x, **y}
    w = {'foo': 'bar', 'baz': 'qux', **y}  # merge a dict with literal values
    
  • Geef in Python 2, (of 3.4 of lager) een functie op:

    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
    

    en

    z = merge_two_dicts(x, y)
    

Uitleg

Stel dat je twee dictaten hebt en je wilt ze samenvoegen tot een nieuw dictaat zonder de originele dictaten te veranderen:

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

Het gewenste resultaat is om een ​​nieuw woordenboek te krijgen (z) waarbij de waarden zijn samengevoegd en de waarden van de tweede dict overschreven die van de eerste.

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

Een nieuwe syntaxis hiervoor, voorgesteld in PEP 448 en beschikbaar vanaf Python 3.5, is

z = {**x, **y}

En het is inderdaad een enkele uitdrukking. Het wordt nu weergegeven zoals geïmplementeerd in de release schema voor 3.5, PEP 478en het heeft nu zijn weg gevonden Wat is nieuw in Python 3.5 document.

Aangezien veel organisaties nog steeds op Python 2 staan, wilt u dit misschien op een achterwaarts compatibele manier doen. De klassieke Pythonic manier, beschikbaar in Python 2 en Python 3.0-3.4, is om dit als een proces in twee stappen te doen:

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

In beide benaderingen, y zal als tweede komen en zijn waarden zullen vervangen worden xde waarden, dus 'b' zal wijzen 3 in ons eindresultaat.

Nog niet op Python 3.5, maar wil een enkele uitdrukking

Als je nog geen lid bent van Python 3.5, of als je een code met een achterwaartse code wilt schrijven, en je wilt dit in a enkele uitdrukking, de meest performante terwijl de juiste benadering is om het in een functie te plaatsen:

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

en dan heb je een enkele uitdrukking:

z = merge_two_dicts(x, y)

U kunt ook een functie maken om een ​​ongedefinieerd aantal dictaten samen te voegen, van nul tot een zeer groot aantal:

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

Deze functie werkt in Python 2 en 3 voor alle dictaten. bijv. gegeven dictaten a naar g:

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

en sleutelwaardeparen in g heeft voorrang op dictaten a naar f, enzovoort.

Kritieken van andere antwoorden

Gebruik niet wat u ziet in het eerder geaccepteerde antwoord:

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

In Python 2 maakt u voor elk dictaat twee lijsten in het geheugen, maakt u een derde lijst in het geheugen waarvan de lengte gelijk is aan de lengte van de eerste twee in elkaar en verwijdert u vervolgens alle drie lijsten om het dictaat te maken. In Python 3 zal dit mislukken omdat je er twee toevoegt dict_items objecten samen, niet twee lijsten -

>>> 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'

en je zou ze expliciet moeten maken als lijsten, bijvoorbeeld z = dict(list(x.items()) + list(y.items())). Dit is verspilling van middelen en rekenkracht.

Evenzo, het nemen van de Unie van items()in Python 3 (viewitems() in Python 2.7) mislukt ook als waarden niet te vernietigen objecten zijn (zoals lijsten). Zelfs als uw waarden hashable zijn, aangezien sets semantisch ongeordend zijn, is het gedrag ongedefinieerd met betrekking tot voorrang. Dus doe dit niet:

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

Dit voorbeeld laat zien wat er gebeurt als waarden niet kunnen worden verwijderd:

>>> 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 is een voorbeeld waarbij y voorrang moet hebben, maar in plaats daarvan wordt de waarde van x behouden door de willekeurige volgorde van sets:

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

Nog een hack die je niet moet gebruiken:

z = dict(x, **y)

Dit gebruikt de dict constructor, en is erg snel en geheugenefficiënt (zelfs iets meer dan ons tweestaps proces) maar tenzij je precies weet wat hier gebeurt (dat wil zeggen, het tweede dictaat wordt doorgegeven als trefwoordargumenten aan de dictator), het is moeilijk te lezen, het is niet het bedoelde gebruik, en dus is het niet Pythisch.

Hier is een voorbeeld van het gebruik gesaneerd in django.

Dictaten zijn bedoeld om hashable-sleutels (bijvoorbeeld frozensets of tuples) te gebruiken, maar deze methode mislukt in Python 3 wanneer sleutels geen tekenreeksen zijn.

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

Van de mailinglijst, Guido van Rossum, de maker van de taal, schreef:

Met mij gaat het goed   declareren ({}, ** {1: 3}) illegaal verklaren, want het is tenslotte misbruik van   het mechanisme.

en

Blijkbaar gaat dict (x, ** y) rond als "coole hack" voor "call"   x.update (y) en retourneer x ". Persoonlijk vind ik het verachtelijker dan   koel.

Het is mijn begrip (evenals het begrip van de maker van de taal) die het bedoelde gebruik voor dict(**y) is voor het maken van dictaten voor leesbaarheidsdoeleinden, bijvoorbeeld:

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

in plaats van

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

Reactie op opmerkingen

Ondanks wat Guido zegt, dict(x, **y) is in overeenstemming met de dict-specificatie, die trouwens. werkt voor zowel Python 2 als 3. Het feit dat dit alleen werkt voor stringtoetsen is een direct gevolg van de manier waarop trefwoordparameters werken en niet een korte aanduiding van dict. Noch is het gebruik van de ** -operator op deze plaats misbruik van het mechanisme, in feite ** was juist ontworpen om dictaten als sleutelwoorden door te geven.

Nogmaals, het werkt niet voor 3 wanneer sleutels niet-strings zijn. Het impliciete aanroepcontract is dat namespaces gewone dictaten aannemen, terwijl gebruikers alleen sleutelwoordargumenten moeten doorgeven die strings zijn. Alle andere callables dwongen het af. dict brak deze consistentie 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}

Deze inconsistentie was slecht gegeven andere implementaties van Python (Pypy, Jython, IronPython). Dus het werd opgelost in Python 3, omdat dit gebruik een brekende verandering kon zijn.

Ik beweer u dat het een kwaadwillende incompetentie is om opzettelijk code te schrijven die alleen in één versie van een taal werkt of die alleen werkt met bepaalde willekeurige beperkingen.

Nog een opmerking:

dict(x.items() + y.items()) is nog steeds de meest leesbare oplossing voor Python 2. Leesbaarheid telt.

Mijn antwoord: merge_two_dicts(x, y) lijkt me eigenlijk veel duidelijker, als we ons eigenlijk zorgen maken over de leesbaarheid. En het is niet voorwaarts compatibel, omdat Python 2 in toenemende mate verouderd is.

Minder performante maar correcte ad-hocs

Deze benaderingen zijn minder performant, maar ze zullen correct gedrag bieden. Zij zullen zijn veel minder performant dan copy en update of het nieuwe uitpakken omdat ze door elk sleutel / waarde-paar op een hoger abstractieniveau heen gaan, maar zij do de volgorde van voorrang respecteren (laatstgenoemde dictaten hebben voorrang)

U kunt de dictaten ook handmatig in een dictaat begrijpen:

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

of in python 2.6 (en misschien al in 2.4 toen generatoruitdrukkingen werden geïntroduceerd):

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

itertools.chain koppelt de iterators over de sleutel / waarde-paren in de juiste volgorde:

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

Prestatie analyse

Ik ga alleen de prestatieanalyse doen van de gebruiken waarvan we weten dat die zich correct gedragen.

import timeit

Het volgende wordt gedaan op Ubuntu 14.04

In Python 2.7 (systeempython):

>>> 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

Bronnen op woordenboeken


3336
2017-11-10 22:11



In uw geval is wat u kunt doen:

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

Dit zal, zoals je het wilt, het laatste dictaat erin stoppen z, en maak de waarde voor de sleutel b correct worden overschreven door de tweede (y) de waarde van dict:

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

Als je Python 3 gebruikt, is het iets ingewikkelder. Maken z:

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

1438
2017-09-02 07:50



Een alternatief:

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

546
2017-09-02 13:00



Een andere, meer beknopte, optie:

z = dict(x, **y)

Notitie: dit is een populair antwoord geworden, maar het is belangrijk om erop te wijzen dat als y heeft geen niet-reeks sleutels, het feit dat dit überhaupt werkt is een misbruik van een CPython implementatiedetail, en het werkt niet in Python 3, of in PyPy, IronPython of Jython. Ook, Guido is geen fan. Dus ik kan deze techniek niet aanbevelen voor voorwaarts compatibele of cross-implementatie draagbare code, wat eigenlijk betekent dat het volledig moet worden vermeden.


272
2017-09-02 15:52



Dit zal waarschijnlijk geen populair antwoord zijn, maar je wilt dit bijna zeker niet doen. Als u een kopie wilt die samenvoegt, gebruikt u de kopie (of deepcopy, afhankelijk van wat je wilt) en update dan. De twee regels code zijn veel leesbaarder - meer Pythonic - dan de creatie van enkele regel met .items () + .items (). Expliciet is beter dan impliciet.

Als u .items () (pre Python 3.0) gebruikt, maakt u bovendien een nieuwe lijst met items uit het dictaat. Als je woordenboeken groot zijn, dan is dat vrij veel overhead (twee grote lijsten die worden weggegooid zodra het samengevoegde dictaat is gemaakt). update () kan efficiënter werken, omdat het item item voor item kan worden gevolgd.

Aangaande met tijd:

>>> 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 de kleine vertraging tussen de eerste twee is de moeite waard voor de leesbaarheid. Bovendien zijn zoekwoordargumenten voor het maken van woordenlijsten alleen toegevoegd in Python 2.3, terwijl kopiëren () en bijwerken () in oudere versies zullen werken.


167
2017-09-08 11:16



In een vervolgantwoord vroeg u naar de relatieve prestaties van deze twee alternatieven:

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

Op mijn machine, tenminste (een vrij gewone x86_64 met Python 2.5.2), alternatief z2 is niet alleen korter en eenvoudiger, maar ook aanzienlijk sneller. U kunt dit zelf verifiëren met behulp van de timeit module die wordt meegeleverd met Python.

Voorbeeld 1: identieke woordenboeken die 20 opeenvolgende gehele getallen aan zichzelf toewijzen:

% 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 wint met een factor 3,5 of zo. Verschillende woordenboeken lijken heel andere resultaten op te leveren, maar z2 lijkt altijd vooruit te komen. (Als u inconsistente resultaten krijgt voor de dezelfde test, probeer binnen te komen -r met een nummer dat groter is dan de standaardwaarde 3.)

Voorbeeld 2: niet-overlappende woordenboeken die 252 korte reeksen toewijzen aan gehele getallen en omgekeerd:

% 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 wint met ongeveer een factor 10. Dat is een vrij grote overwinning in mijn boek!

Na het vergelijken van die twee, vroeg ik me af of z1De slechte prestaties konden worden toegeschreven aan de overhead van het samenstellen van de twee itemlijsten, waardoor ik me afvroeg of deze variant beter zou werken:

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

Enkele snelle tests, b.v.

% 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

leid me om dat te concluderen z3 is iets sneller dan z1, maar lang niet zo snel als z2. Zeker niet het extra typen waard.

Deze discussie mist nog iets belangrijks, namelijk een prestatievergelijking van deze alternatieven met de "voor de hand liggende" manier om twee lijsten samen te voegen: het gebruik van de update methode. Om te proberen de dingen op gelijke voet te houden met de uitdrukkingen, die geen van beide x of y wijzigen, ga ik een kopie maken van x in plaats van het op zijn plaats te wijzigen, en wel als volgt:

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

Een typisch resultaat:

% 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

Met andere woorden, z0 en z2 lijken in wezen identieke prestaties te hebben. Denk je dat dit een toeval zou kunnen zijn? Ik niet....

Sterker nog, ik zou zo ver willen gaan om te beweren dat het voor pure Python-code onmogelijk is om het beter te doen dan dit. En als je het aanzienlijk beter kunt doen in een C-uitbreidingsmodule, stel ik me voor dat de Python-mensen wellicht geïnteresseerd zijn in het opnemen van je code (of een variant op je aanpak) in de kern van Python. Python gebruikt dict op veel plaatsen; het optimaliseren van zijn activiteiten is een groot probleem.

Je zou dit ook kunnen schrijven als

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

zoals Tony doet, maar (niet verrassend) blijkt het verschil in notatie geen meetbaar effect te hebben op de prestaties. Gebruik wat je goed lijkt. Natuurlijk is hij absoluut correct om erop te wijzen dat de versie met twee verklaringen veel gemakkelijker te begrijpen is.


116
2017-10-23 02:38



Ik wilde iets vergelijkbaars, maar met de mogelijkheid om aan te geven hoe de waarden op dubbele sleutels werden samengevoegd, dus ik heb dit gehackt (maar heb het niet zwaar getest). Dit is duidelijk geen enkele uitdrukking, maar het is een oproep met één functie.

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 kun je gebruiken collections.ChainMap waarin meerdere dictaten of andere toewijzingen worden samengevoegd om een ​​enkele, te wijzigen weergave te maken:

>>> 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



Recursief / diep een dictaat bijwerken

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

Demonstratie:

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

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

print deepupdate(pluto_original, pluto_update)

uitgangen:

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

Bedankt rednaw voor bewerkingen.


61
2017-11-29 11:52



De beste versie die ik zou kunnen bedenken als ik geen kopie zou gebruiken, zou zijn:

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

Het is sneller dan dict(x.items() + y.items()) maar niet zo snel als n = copy(a); n.update(b), tenminste op CPython. Deze versie werkt ook in Python 3 als je verandert iteritems() naar items(), wat automatisch wordt gedaan door de 2to3 tool.

Persoonlijk vind ik deze versie het beste omdat het redelijk goed beschrijft wat ik wil in een enkele functionele syntaxis. Het enige kleine probleem is dat het niet helemaal duidelijk maakt dat waarden uit y voorrang hebben boven waarden van x, maar ik geloof niet dat het moeilijk is om dat uit te zoeken.


54
2017-10-14 18:55



Python 3.5 (PEP 448) staat een leukere syntaxisoptie toe:

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

Of zelfs

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

41
2018-02-26 21:27