Question Comment fusionner deux dictionnaires dans une même expression?


J'ai deux dictionnaires Python, et je veux écrire une seule expression qui renvoie ces deux dictionnaires, fusionnés. le update() méthode serait ce dont j'ai besoin, si elle renvoyait son résultat au lieu de modifier un dict en place.

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

Comment puis-je obtenir cette dict finale fusionnée z, ne pas x?

(Pour être plus clair, le dernier conflit gagne la gestion des conflits dict.update() est ce que je cherche aussi.)


3211
2017-09-02 07:44


origine


Réponses:


Comment puis-je fusionner deux dictionnaires Python dans une même expression?

Pour les dictionnaires x et y, z devient un dictionnaire fusionné avec des valeurs de y remplacer ceux de x.

  • En Python 3.5 ou supérieur,

    z = {**x, **y}
    w = {'foo': 'bar', 'baz': 'qux', **y}  # merge a dict with literal values
    
  • En Python 2, (ou 3.4 ou moins) écrire une fonction:

    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
    

    et

    z = merge_two_dicts(x, y)
    

Explication

Disons que vous avez deux dictes et que vous voulez les fusionner en une nouvelle dict sans altérer les dits originaux:

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

Le résultat souhaité est d'obtenir un nouveau dictionnaire (z) avec les valeurs fusionnées, et les valeurs du second dict écrasant celles du premier.

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

Une nouvelle syntaxe pour cela, proposée dans PEP 448 et disponible à partir de Python 3.5, est

z = {**x, **y}

Et c'est en effet une seule expression. Il montre maintenant comme mis en œuvre dans le calendrier de publication pour 3.5, PEP 478, et il a maintenant fait son chemin dans Quoi de neuf dans Python 3.5 document.

Cependant, étant donné que de nombreuses organisations sont toujours sur Python 2, vous pouvez le faire de manière compatible avec les versions précédentes. La manière classique de Pythonic, disponible dans Python 2 et Python 3.0-3.4, est de le faire en deux étapes:

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

Dans les deux approches, y viendra en second lieu et ses valeurs remplaceront xles valeurs, donc 'b' pointera vers 3 dans notre résultat final.

Pas encore sur Python 3.5, mais vous voulez un expression unique

Si vous n'êtes pas encore sur Python 3.5, ou si vous avez besoin d'écrire du code rétrocompatible, et vous le voulez dans un expression unique, l'approche la plus performante tandis que correcte est de le mettre dans une fonction:

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

et alors vous avez une seule expression:

z = merge_two_dicts(x, y)

Vous pouvez également créer une fonction pour fusionner un nombre indéfini de dicts, de zéro à un très grand nombre:

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

Cette fonction fonctionnera en Python 2 et 3 pour tous les dicts. par exemple. Dictés donnés a à g:

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

et paires de valeurs clés dans g aura préséance sur les dicts a à f, etc.

Critiques d'autres réponses

N'utilisez pas ce que vous voyez dans la réponse précédemment acceptée:

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

Dans Python 2, vous créez deux listes en mémoire pour chaque dict, créez une troisième liste en mémoire d'une longueur égale à la longueur des deux premières assemblées, puis éliminez les trois listes pour créer la dict. En Python 3, cela échouera parce que vous ajoutez deux dict_items objets ensemble, pas deux listes -

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

et vous devrez les créer explicitement en tant que listes, par ex. z = dict(list(x.items()) + list(y.items())). C'est un gaspillage de ressources et de puissance de calcul.

De même, en prenant l'union de items()en Python 3 (viewitems() dans Python 2.7) échouera également lorsque les valeurs sont des objets non-vérifiables (comme des listes, par exemple). Même si vos valeurs sont aissables, Puisque les ensembles sont sémantiquement non ordonnés, le comportement n'est pas défini en ce qui concerne la précédence. Donc, ne faites pas ceci:

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

Cet exemple montre ce qui se passe lorsque les valeurs ne sont pas vérifiables:

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

Voici un exemple où y devrait avoir la priorité, mais à la place la valeur de x est retenue en raison de l'ordre arbitraire des ensembles:

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

Un autre hack que vous ne devriez pas utiliser:

z = dict(x, **y)

Cela utilise le dict constructeur, et est très rapide et efficace en mémoire (même légèrement plus que notre processus en deux étapes) mais à moins que vous sachiez exactement ce qui se passe ici (c'est-à-dire que le second dict est passé en argument au constructeur dict) c'est difficile à lire, ce n'est pas l'usage prévu, et donc ce n'est pas Pythonic.

Voici un exemple de l'utilisation étant restauré dans django.

Les dits sont destinés à prendre des clés (par exemple des frozensets ou des tuples), mais cette méthode échoue dans Python 3 lorsque les clés ne sont pas des chaînes.

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

Du liste de diffusion, Guido van Rossum, le créateur de la langue, a écrit:

Je vais bien avec   déclarer dict ({}, ** {1: 3}) illégal, car après tout c'est un abus de   Le mécanisme.

et

Apparemment dict (x, ** y) se passe comme "cool hack" pour "appeler   x.update (y) et return x "Personnellement, je le trouve plus méprisable que   cool.

C'est ma compréhension (ainsi que la compréhension de créateur de la langue) que l'utilisation prévue dict(**y) est pour créer des dicts à des fins de lisibilité, par exemple:

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

au lieu de

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

Réponse aux commentaires

Malgré ce que Guido dit, dict(x, **y) est en ligne avec la spécification dict, qui btw. fonctionne à la fois pour Python 2 et 3. Le fait que cela ne fonctionne que pour les clés de chaîne est une conséquence directe de la façon dont les paramètres de mots-clés fonctionnent et non un raccourci de dict. Pas plus que l'utilisation de l'opérateur ** dans ce lieu un abus du mécanisme, en fait ** a été conçu précisément pour faire passer des dicts comme des mots-clés.

Encore une fois, cela ne fonctionne pas pour 3 lorsque les clés sont non-chaînes. Le contrat d'appel implicite est que les espaces de noms prennent des dicts ordinaires, tandis que les utilisateurs doivent seulement passer des arguments de mot clé qui sont des chaînes. Tous les autres appelables l'ont appliqué. dict brisé cette cohérence dans 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}

Cette incohérence était mauvaise compte tenu d'autres implémentations de Python (Pypy, Jython, IronPython). Ainsi, il a été corrigé dans Python 3, car cette utilisation pourrait être un changement de rupture.

Je vous soumets que c'est une incompétence malveillante d'écrire intentionnellement du code qui ne fonctionne que dans une version d'une langue ou qui ne fonctionne que dans certaines contraintes arbitraires.

Un autre commentaire:

dict(x.items() + y.items()) est toujours la solution la plus lisible pour Python 2. La lisibilité compte.

Ma réponse: merge_two_dicts(x, y) Cela me semble beaucoup plus clair si nous sommes réellement préoccupés par la lisibilité. Et il n'est pas compatible avec Python 2, car il est de plus en plus obsolète.

Ad-hocs moins performants mais corrects

Ces approches sont moins performantes, mais elles fourniront un comportement correct. Ils seront beaucoup moins performant que copy et update ou le nouveau déballage, car ils parcourent chaque paire clé-valeur à un niveau d'abstraction plus élevé, mais ils faire respecter l'ordre de préséance (les derniers dicts ont préséance)

Vous pouvez également chaîner les dicts manuellement dans une compréhension de dictée:

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

ou en python 2.6 (et peut-être aussi tôt que 2.4 quand les expressions génératrices ont été introduites):

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

itertools.chain enchaînera les itérateurs sur les paires clé-valeur dans le bon ordre:

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

Analyse de performance

Je vais seulement faire l'analyse de performance des usages connus pour se comporter correctement.

import timeit

Ce qui suit est fait sur Ubuntu 14.04

En Python 2.7 (système 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

En Python 3.5 (PPA de Deadsnakes):

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

Ressources sur les dictionnaires


3336
2017-11-10 22:11



Dans votre cas, ce que vous pouvez faire est:

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

Cela va, comme vous le voulez, mettre la dernière dict zet faites la valeur pour la clé b être correctement remplacé par le second (y) Valeur de dict:

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

Si vous utilisez Python 3, c'est un peu plus compliqué. Créer z:

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

1438
2017-09-02 07:50



Une alternative:

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

546
2017-09-02 13:00



Une autre option plus concise:

z = dict(x, **y)

Remarque: ceci est devenu une réponse populaire, mais il est important de souligner que si y a toutes les clés non-chaîne, le fait que cela fonctionne est un abus d'un détail d'implémentation de CPython, et cela ne fonctionne pas dans Python 3, ou dans PyPy, IronPython, ou Jython. Aussi, Guido n'est pas fan. Je ne peux donc pas recommander cette technique pour du code portable compatible avec la mise en œuvre vers l'avant ou la mise en œuvre croisée, ce qui signifie qu'il faut l'éviter complètement.


272
2017-09-02 15:52



Ce ne sera probablement pas une réponse populaire, mais vous ne voulez certainement pas le faire. Si vous voulez une copie fusionnée, utilisez copy (ou deepcopy, en fonction de ce que vous voulez), puis mettre à jour. Les deux lignes de code sont beaucoup plus lisibles - plus Pythonic - que la simple ligne avec .items () + .items (). Explicite est mieux qu'implicite.

En outre, lorsque vous utilisez .items () (pré Python 3.0), vous créez une nouvelle liste contenant les éléments de dict. Si vos dictionnaires sont volumineux, il y a beaucoup de surcharge (deux grandes listes qui seront jetées dès que le dict fusionné sera créé). update () peut fonctionner plus efficacement, car il peut parcourir le deuxième dict article par article.

En terme de temps:

>>> 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 le minuscule ralentissement entre les deux premiers en vaut la peine pour la lisibilité. En outre, les arguments de mots-clés pour la création de dictionnaire ont été ajoutés uniquement dans Python 2.3, alors que copy () et update () fonctionneront dans les anciennes versions.


167
2017-09-08 11:16



Dans une réponse de suivi, vous avez posé des questions sur la performance relative de ces deux alternatives:

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

Sur ma machine, au moins (un x86_64 assez courant exécutant Python 2.5.2), alternative z2 est non seulement plus court et plus simple mais aussi beaucoup plus rapide. Vous pouvez vérifier cela par vous-même en utilisant le timeit module qui vient avec Python.

Exemple 1: dictionnaires identiques mappant 20 entiers consécutifs à eux-mêmes:

% 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 gagne par un facteur de 3,5 ou plus. Différents dictionnaires semblent donner des résultats assez différents, mais z2 semble toujours sortir de l'avant. (Si vous obtenez des résultats incohérents pour le même tester, essayer de passer -r avec un nombre plus grand que la valeur par défaut 3.)

Exemple 2: dictionnaires sans chevauchement mappant 252 chaînes courtes à des entiers et vice versa:

% 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 gagne environ un facteur de 10. C'est une belle victoire dans mon livre!

Après avoir comparé ces deux, je me demandais si z1La performance médiocre pourrait être attribuée aux frais généraux de construction des deux listes d'articles, ce qui m'a conduit à me demander si cette variation pourrait mieux fonctionner:

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

Quelques tests rapides, par ex.

% 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

conduis-moi à conclure que z3 est un peu plus rapide que z1, mais pas aussi vite que z2. Certainement pas la peine de tous les dactylographie supplémentaire.

Cette discussion manque toujours quelque chose d'important, qui est une comparaison de performance de ces alternatives avec la manière «évidente» de fusionner deux listes: en utilisant le update méthode. Pour essayer de garder les choses sur un pied d'égalité avec les expressions, dont aucune ne modifie x ou y, je vais faire une copie de x au lieu de le modifier sur place, comme suit:

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

Un résultat typique:

% 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

En d'autres termes, z0 et z2 semblent avoir des performances essentiellement identiques. Pensez-vous que cela pourrait être une coïncidence? Je ne....

En fait, j'irais jusqu'à prétendre qu'il est impossible que le pur code Python fasse mieux que ça. Et si vous pouvez faire beaucoup mieux dans un module d'extension C, j'imagine que les gens de Python pourraient bien être intéressés à intégrer votre code (ou une variation de votre approche) dans le noyau de Python. Python utilise dict dans beaucoup d'endroits; optimiser ses opérations est une grosse affaire.

Vous pouvez aussi écrire ceci comme

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

comme le fait Tony, mais (ce qui n'est pas surprenant) la différence de notation s'avère n'avoir aucun effet mesurable sur la performance. Utilisez celui qui vous convient le mieux. Bien sûr, il a absolument raison de souligner que la version à deux énoncés est beaucoup plus facile à comprendre.


116
2017-10-23 02:38



Je voulais quelque chose de similaire, mais avec la possibilité de spécifier comment les valeurs sur les clés en double ont été fusionnées, alors j'ai piraté cela (mais je ne l'ai pas testé intensivement). Évidemment, ce n'est pas une seule expression, mais c'est un appel à une seule fonction.

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



En Python 3, vous pouvez utiliser collections.ChainMap qui regroupe plusieurs dicts ou autres mappages pour créer une seule vue pouvant être mise à jour:

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