Como mesclar dois dicionários em uma única expressão no Python?


4787

Eu tenho dois dicionários Python e quero escrever uma única expressão que retorne esses dois dicionários, mesclados. O update()método seria o que eu precisava, se retornasse o resultado em vez de modificar um dicionário no local.

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

Como posso obter esse dicionário mesclado final z, não x?

(Para ser mais claro, o último que ganha é a manipulação de conflitos, dict.update()é o que estou procurando também.)


1
Se você estiver usando o Python 3.9 alfa, usez = x | y
The Daleks

Respostas:


5704

Como mesclar dois dicionários Python em uma única expressão?

Para dicionários xe y, ztorna-se um dicionário superficialmente mesclado com valores da ysubstituição daqueles de x.

  • No Python 3.5 ou superior:

    z = {**x, **y}
  • No Python 2, (ou 3.4 ou menos) escreva uma função:

    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

    e agora:

    z = merge_two_dicts(x, y)
  • No Python 3.9.0a4 ou superior (data de lançamento final aproximadamente outubro de 2020): PEP-584 , discutido aqui , foi implementado para simplificar ainda mais isso:

    z = x | y          # NOTE: 3.9+ ONLY

Explicação

Digamos que você tenha dois ditados e deseja mesclá-los em um novo ditado sem alterar os ditados originais:

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

O resultado desejado é obter um novo dicionário (z ) com os valores mesclados, e os valores do segundo ditado substituindo os do primeiro.

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

Uma nova sintaxe para isso, proposta no PEP 448 e disponível a partir do Python 3.5 , é

z = {**x, **y}

E é de fato uma expressão única.

Observe que também podemos nos fundir com notação literal:

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

e agora:

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

Agora está sendo exibido conforme implementado no cronograma de lançamento do 3.5, PEP 478 , e agora chegou ao What's New in Python 3.5 .

No entanto, como muitas organizações ainda estão no Python 2, convém fazer isso de uma maneira compatível com versões anteriores. A maneira classicamente Pythonic, disponível no Python 2 e Python 3.0-3.4, é fazer isso como um processo de duas etapas:

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

Em ambas as abordagens, yvirá segunda e seus valores irá substituir x's valores, portanto, 'b'irá apontar para3 nosso resultado final.

Ainda não está no Python 3.5, mas quer um única expressão

Se você ainda não está no Python 3.5 ou precisa escrever um código compatível com versões anteriores, e deseja isso em uma única expressão , o melhor desempenho enquanto a abordagem correta é colocá-lo em uma função:

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

e então você tem uma única expressão:

z = merge_two_dicts(x, y)

Você também pode criar uma função para mesclar um número indefinido de dictos, de zero a um número muito grande:

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

Esta função funcionará no Python 2 e 3 para todos os dictos. por exemplo, ditados dados apara g:

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

e os pares de valores-chave gterão precedência sobre os ditados apara fe assim por diante.

Críticas de outras respostas

Não use o que vê na resposta anteriormente aceita:

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

No Python 2, você cria duas listas na memória para cada ditado, cria uma terceira lista na memória com comprimento igual ao comprimento dos dois primeiros juntos e, em seguida, descarta as três listas para criar o ditado. No Python 3, isso falhará porque você adiciona dois dict_itemsobjetos, não duas listas -

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

e você teria que criá-los explicitamente como listas, por exemplo z = dict(list(x.items()) + list(y.items())). Isso é um desperdício de recursos e poder computacional.

Da mesma forma, aceitar a união do items()Python 3 ( viewitems()no Python 2.7) também falhará quando os valores forem objetos laváveis ​​(como listas, por exemplo). Mesmo que seus valores sejam hasháveis, uma vez que os conjuntos são semanticamente desordenados, o comportamento é indefinido em relação à precedência. Então não faça isso:

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

Este exemplo demonstra o que acontece quando os valores são laváveis:

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

Aqui está um exemplo em que y deve ter precedência, mas o valor de x é retido devido à ordem arbitrária dos conjuntos:

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

Outro truque que você não deve usar:

z = dict(x, **y)

Isso usa o dictconstrutor e é muito rápido e eficiente em memória (até um pouco mais do que o nosso processo de duas etapas), mas a menos que você saiba exatamente o que está acontecendo aqui (ou seja, o segundo ditado está sendo passado como argumentos de palavra-chave para o ditado construtor), é difícil de ler, não é o uso pretendido e, portanto, não é Pythonic.

Aqui está um exemplo do uso sendo remediado no django .

Os dicts destinam-se a levar chaves hash (por exemplo, frozensets ou tuplas), mas esse método falha no Python 3 quando as chaves não são cadeias de caracteres.

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

Da lista de discussão , Guido van Rossum, o criador da linguagem, escreveu:

Estou bem em declarar o dict ({}, ** {1: 3}) ilegal, pois, afinal, é abuso do mecanismo **.

e

Aparentemente, o dict (x, ** y) está sendo exibido como "cool hack" para "chame x.update (y) e retorne x". Pessoalmente, acho isso mais desprezível do que legal.

Entendo (assim como o criador da linguagem ) que o uso pretendido dict(**y)é para criar dictos para fins de legibilidade, por exemplo:

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

ao invés de

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

Resposta a comentários

Apesar do que Guido diz, dict(x, **y)está alinhado com a especificação de ditado, que se encaixa. funciona para Python 2 e 3. O fato de que isso funciona apenas para chaves de seqüência de caracteres é uma conseqüência direta de como os parâmetros de palavra-chave funcionam e não um ditado. O uso do ** operador neste local também não é um abuso do mecanismo, de fato ** foi projetado precisamente para passar dict como palavras-chave.

Novamente, ele não funciona para 3 quando as chaves não são cadeias de caracteres. O contrato de chamada implícita é que os namespaces assumem dicionários comuns, enquanto os usuários devem passar apenas argumentos de palavras-chave que são strings. Todos os outros callables o aplicaram. dictquebrou essa consistência no 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}

Essa inconsistência foi ruim, devido a outras implementações do Python (Pypy, Jython, IronPython). Portanto, ele foi corrigido no Python 3, pois esse uso pode ser uma mudança de última hora.

Eu afirmo a você que é incompetência maliciosa escrever intencionalmente código que funciona apenas em uma versão de um idioma ou que funciona apenas devido a certas restrições arbitrárias.

Mais comentários:

dict(x.items() + y.items()) ainda é a solução mais legível para o Python 2. A legibilidade conta.

Minha resposta: merge_two_dicts(x, y)na verdade, parece muito mais claro para mim, se estamos realmente preocupados com a legibilidade. E não é compatível com o futuro, pois o Python 2 está cada vez mais obsoleto.

{**x, **y}parece não manipular dicionários aninhados. o conteúdo das chaves aninhadas é simplesmente sobrescrito, não mesclado [...] acabei sendo queimado por essas respostas que não se mesclam recursivamente e fiquei surpreso que ninguém mencionou. Na minha interpretação da palavra "mesclando", essas respostas descrevem "atualizando um ditado com outro", e não mesclando.

Sim. Devo encaminhá-lo de volta à pergunta, que está pedindo uma mesclagem superficial de dois dicionários, com os valores do primeiro sendo substituídos pelos do segundo - em uma única expressão.

Supondo que dois dicionários de dicionários, um pode recursivamente mesclá-los em uma única função, mas você deve tomar cuidado para não modificar os dictos de nenhuma das fontes, e a maneira mais segura de evitar isso é fazer uma cópia ao atribuir valores. Como as chaves devem ser laváveis ​​e geralmente são imutáveis, não faz sentido copiá-las:

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

Uso:

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

A criação de contingências para outros tipos de valor está muito além do escopo desta pergunta. Por isso, apontarei a minha resposta à pergunta canônica em uma "Mesclagem de dicionários de dicionários" .

Menos desempenho, mas ad-hocs corretos

Essas abordagens têm menos desempenho, mas fornecerão o comportamento correto. Eles serão muito menos eficaz do que copye updateou o novo descompactação porque iterar cada par de valores-chave em um nível mais alto de abstração, mas eles fazer respeitar a ordem de precedência (últimos dicts têm precedência)

Você também pode encadear os dictos manualmente dentro de uma compreensão de dict:

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

ou no python 2.6 (e talvez já no 2.4 quando expressões de gerador foram introduzidas):

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

itertools.chain irá encadear os iteradores sobre os pares de valores-chave na ordem correta:

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

Análise de desempenho

Eu só vou fazer a análise de desempenho dos usos conhecidos por se comportar corretamente.

import timeit

O seguinte é feito no Ubuntu 14.04

No Python 2.7 (sistema 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

No Python 3.5 (PPA mortal):

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

Recursos em dicionários


9
@MohammadAzim "somente strings" se aplica apenas à expansão do argumento de palavra-chave em chamadas, não à sintaxe de descompactação generalizada. Para demonstrar que isso funciona: {**{(0, 1):2}}->{(0, 1): 2}
Aaron Hall

37
respostas curtas como z = {**x, **y}realmente estimular me
pcko1

1
Isso pode ser alterado quando o PEP-0584 for aceito. Um novo operador sindical será implementado com a seguinte sintaxe:x | y
Callam Delaney

2
Quando uma resposta precisa de um resumo no topo, é muito longa.
Gringo Suave

2
Olá, o topo é um resumo, sim. Você decide. A coisa toda seria uma ótima postagem no blog. Nota Py 3.4 e abaixo são EOL, 3.5 se aproximando da EOL em 2020-09.
Gringo Suave

1618

No seu caso, o que você pode fazer é:

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

Isso, como você quiser, coloca o comando final ze faz com que o valor da chave bseja substituído adequadamente pelo valor do segundo ycomando ( ):

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

Se você usa o Python 3, é apenas um pouco mais complicado. Para criar z:

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

Se você usa o Python versão 3.9.0a4 ou superior, pode usar diretamente:

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

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

3
Não use isso, pois é muito ineficiente. (Veja a hora em que resulta abaixo.) Pode ter sido necessário nos dias do Py2 se uma função de wrapper não fosse uma opção, mas esses dias já passaram.
Gringo Suave

632

Uma alternativa:

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

83
Para esclarecer por que isso não atende aos critérios fornecidos pela pergunta: não é uma expressão única e não retorna z.
21413 Alex

2
@neuronet todo oneliner geralmente está apenas movendo o código que precisa acontecer em um componente diferente e o resolve lá. esse é definitivamente um dos casos. mas outras linguagens têm construções melhores do que python para isso. e ter uma variante referencialmente transparente que retorna seu elemento é uma coisa boa de se ter.
Alex

12
Coloque desta forma: se você precisar colocar duas linhas de comentários explicando sua única linha de código para as pessoas para quem você entrega seu código ... você realmente fez isso em uma linha? :) Concordo plenamente que o Python não é bom para isso: deve haver uma maneira muito mais fácil. Embora essa resposta seja mais pitônica, é realmente tudo isso explícito ou claro? Updatenão é uma das funções "essenciais" que as pessoas costumam usar muito.
19617

Bem, se as pessoas insistem em torná-lo um oneliner, você sempre pode (lambda z: z.update(y) or z)(x.copy()): P
towr

341

Outra opção mais concisa:

z = dict(x, **y)

Nota : essa se tornou uma resposta popular, mas é importante ressaltar que, se yhouver alguma chave não-string, o fato de que isso funcione é um abuso de detalhes de implementação do CPython e não funciona no Python 3, ou em PyPy, IronPython ou Jython. Além disso, Guido não é fã . Portanto, não posso recomendar esta técnica para código portátil compatível com a frente ou de implementação cruzada, o que realmente significa que deve ser totalmente evitado.


Funciona bem em Python 3 e PyPy e PyPy 3 , não pode falar com Jython ou Iron. Dado que esse padrão é explicitamente documentado (consulte o terceiro formulário do construtor nesta documentação), eu argumentaria que não é um "detalhe de implementação", mas o uso intencional de recursos.
amcgregor

5
@amcgregor Você perdeu a frase-chave "se y tiver alguma chave não-string". É isso que não funciona no Python3; o fato de funcionar no CPython 2 é um detalhe de implementação em que não se pode confiar. Se todas as suas chaves são garantidas como strings, esta é uma opção totalmente suportada.
Carl Meyer

214

Provavelmente, essa não será uma resposta popular, mas você quase certamente não deseja fazer isso. Se você deseja uma cópia mesclada, use copy (ou deepcopy , dependendo do que você deseja) e atualize. As duas linhas de código são muito mais legíveis - mais Pythonic - do que a criação de uma única linha com .items () + .items (). Explícito é melhor que implícito.

Além disso, quando você usa .items () (antes do Python 3.0), está criando uma nova lista que contém os itens do dict. Se seus dicionários forem grandes, isso representa uma sobrecarga considerável (duas listas grandes que serão jogadas fora assim que o ditado mesclado for criado). update () pode funcionar com mais eficiência, porque pode ser executado através do segundo ditado item por item.

Em termos de tempo :

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

Na OMI, a pequena desaceleração entre os dois primeiros vale a pena pela legibilidade. Além disso, argumentos de palavras-chave para criação de dicionário foram adicionados apenas no Python 2.3, enquanto copy () e update () funcionarão em versões mais antigas.


150

Em uma resposta de acompanhamento, você perguntou sobre o desempenho relativo dessas duas alternativas:

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

Na minha máquina, pelo menos (um x86_64 bastante comum executando o Python 2.5.2), a alternativa z2não é apenas mais curta e mais simples, mas também significativamente mais rápida. Você pode verificar isso por si mesmo usando otimeit módulo que acompanha o Python.

Exemplo 1: dicionários idênticos mapeando 20 números inteiros consecutivos para si mesmos:

% 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

z2ganha por um fator de 3,5 ou mais. Dicionários diferentes parecem produzir resultados bastante diferentes, mas z2sempre parecem sair à frente. (Se você obtiver resultados inconsistentes para o mesmo teste, tente passar -rcom um número maior que o padrão 3).

Exemplo 2: dicionários sem sobreposição mapeando 252 cadeias curtas para números inteiros e 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 ganha por um fator de 10. Essa é uma grande vitória no meu livro!

Depois de comparar esses dois, perguntei-me se z1o baixo desempenho poderia ser atribuído à sobrecarga de construir as duas listas de itens, o que me levou a pensar se essa variação poderia funcionar melhor:

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

Alguns testes rápidos, por exemplo

% 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

levou-me a concluir que z3é um pouco mais rápido do que z1, mas não tão rápido quantoz2 . Definitivamente não vale toda a digitação extra.

Ainda falta a essa discussão algo importante, que é uma comparação de desempenho dessas alternativas com a maneira "óbvia" de mesclar duas listas: usando o updatemétodo Para tentar manter as coisas em pé de igualdade com as expressões, nenhuma das quais modifica x ou y, vou fazer uma cópia de x em vez de modificá-la no local, da seguinte maneira:

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

Um resultado típico:

% 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

Em outras palavras, z0e z2parecem ter desempenho essencialmente idêntico. Você acha que isso pode ser uma coincidência? Eu não....

Na verdade, eu chegaria ao ponto de afirmar que é impossível para o código Python puro fazer melhor que isso. E se você puder se sair significativamente melhor em um módulo de extensão C, imagino que o pessoal do Python possa estar interessado em incorporar seu código (ou uma variação na sua abordagem) ao núcleo do Python. Python usadict em muitos lugares; otimizar suas operações é um grande negócio.

Você também pode escrever isso como

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

como Tony faz, mas (não surpreendentemente) a diferença na notação acaba por não ter nenhum efeito mensurável no desempenho. Use o que parecer melhor para você. Obviamente, ele está absolutamente correto ao apontar que a versão de duas instruções é muito mais fácil de entender.


5
Isso não funciona no Python 3; items()não é passível de criação e iteritemsnão existe.
Antti Haapala

127

No Python 3.0 e posterior , você pode usar collections.ChainMapquais grupos vários dictos ou outros mapeamentos juntos para criar uma visão única e atualizável:

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

Atualização para o Python 3.5 e posterior : Você pode usar o dicionário estendido do PEP 448 compactando e descompactando. Isso é rápido e fácil:

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

3
Mas é preciso ter cuidado ao usar o ChainMap, pois se você tiver chaves duplicadas, os valores do primeiro mapeamento delserão usados ​​e, quando você chamar um, digamos que um ChainMap c exclua o primeiro mapeamento dessa chave.
Slayer

7
@Prerit O que mais você espera que ele faça? É assim que os namespaces encadeados funcionam. Considere como o $ PATH funciona no bash. A exclusão de um executável no caminho não exclui outro executável com o mesmo nome mais a montante.
Raymond Hettinger

2
@ Raymond Hettinger Concordo, acabei de adicionar uma cautela. A maioria das pessoas pode não saber sobre isso. : D
Slayer

@Prerit Você pode converter para dictevitar isso, ou seja:dict(ChainMap({}, y, x))
wjandrea 15/07/19

113

Eu queria algo semelhante, mas com a capacidade de especificar como os valores nas chaves duplicadas foram mesclados, então resolvi isso (mas não o testei muito). Obviamente, essa não é uma expressão única, mas é uma chamada de função única.

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

88

Atualizar um ditado de forma recursiva / profunda

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

Demonstração:

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

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

print deepupdate(pluto_original, pluto_update)

Saídas:

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

Obrigado rednaw por edições.


1
Isso não responde à pergunta. A pergunta pede claramente um novo dicionário, z, dos dicionários originais, xey, com os valores de y substituindo os de x - não um dicionário atualizado. Esta resposta modifica y no local adicionando valores de x. Pior, ele não copia esses valores; portanto, é possível modificar ainda mais o dicionário modificado, y, e as modificações podem ser refletidas no dicionário x. @ Jérôme Espero que este código não esteja causando nenhum bug no seu aplicativo - pelo menos considere usar o deepcopy para copiar os valores.
Aaron Hall

1
@AaronHall concordou que isso não responde à pergunta. Mas responde a minha necessidade. Entendo essas limitações, mas isso não é um problema no meu caso. Pensando nisso, talvez o nome seja enganador, pois pode evocar uma cópia em profundidade, que ele não fornece. Mas trata de aninhamento profundo. Aqui está outra implementação do Martellibot: stackoverflow.com/questions/3232943/… .
Jérôme

72

A melhor versão que eu poderia pensar sem usar a cópia seria:

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

É mais rápido que, dict(x.items() + y.items())mas não tão rápido quanto n = copy(a); n.update(b), pelo menos no CPython. Esta versão também funciona no Python 3 se você mudar iteritems()para items(), o que é feito automaticamente pela ferramenta 2to3.

Pessoalmente, eu gosto mais desta versão porque ela descreve razoavelmente bem o que eu quero em uma única sintaxe funcional. O único pequeno problema é que não fica completamente óbvio que os valores de y têm precedência sobre os valores de x, mas não acredito que seja difícil descobrir isso.


71

O Python 3.5 (PEP 448) permite uma opção de sintaxe melhor:

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

Ou até

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

No Python 3.9 você também usa | e | = com o exemplo abaixo do PEP 584

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}

De que maneira essa solução é melhor que a dict(x, **y)solução-? Como você (@CarlMeyer) mencionou na nota de sua própria resposta ( stackoverflow.com/a/39858/2798610 ), Guido considera essa solução ilegal .
precisa saber é o seguinte

14
O Guido não gosta dict(x, **y)pelo (muito bom) motivo de confiar yapenas em chaves que são nomes válidos de argumentos de palavras-chave (a menos que você esteja usando o CPython 2.7, onde o construtor de dict engana). Esta objeção / restrição não se aplica ao PEP 448, que generaliza a **sintaxe de descompactação para ditar literais. Portanto, esta solução tem a mesma concisão dict(x, **y), sem a desvantagem.
Carl Meyer

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

Para itens com chaves nos dois dicionários ('b'), você pode controlar qual deles termina na saída colocando esse último.


No python 3, você obteria TypeError: tipo (s) de operando não suportado para +: 'dict_items' e 'dict_items' ... você deve encapsular cada dict com list () como: dict (list (x.items ()) + list (y.items ()))
justSaid

49

Embora a pergunta já tenha sido respondida várias vezes, esta solução simples para o problema ainda não foi listada.

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

É tão rápido quanto z0 e o z2 mal mencionado acima, mas fácil de entender e mudar.


3
mas é três declarações em vez de uma expressão
Fortran

14
Sim! As soluções de expressão única mencionadas são lentas ou más. Um bom código é legível e de manutenção. Portanto, o problema é a questão, não a resposta. Deveríamos pedir a melhor solução de um problema, não uma solução de uma linha.
Phobie 28/10/11

7
Perder z4 = {}e alterar a próxima linha para z4 = x.copy()- melhor do que apenas um bom código não faz coisas desnecessárias (o que o torna ainda mais legível e sustentável).
martineau

3
Sua sugestão mudaria isso para a resposta de Matthews. Embora a resposta dele seja boa, acho que a minha é mais legível e melhor de manter. A linha extra só seria ruim se custasse tempo de execução.
Phobie

47
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

Entre essas respostas duvidosas e duvidosas, este exemplo brilhante é a única maneira de mesclar ditados em Python, endossado pelo ditador vitalício Guido van Rossum ! Alguém sugeriu metade disso, mas não o colocou em uma função.

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

dá:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}

39

Se você acha que as lambdas são más, não leia mais. Conforme solicitado, você pode escrever a solução rápida e com eficiência de memória com uma expressão:

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}

Como sugerido acima, usar duas linhas ou escrever uma função é provavelmente o melhor caminho a percorrer.


33

Seja pitônico. Use uma compreensão :

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

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

1
Como uma função:def dictmerge(*args): return {i:d[i] for d in args for i in d}
jessexknight 06/06

1
Salve uma pesquisa iterando os pares de chave / valor diretamente:z={k: v for d in (x, y) for k, v in d.items()}
ShadowRanger

30

No python3, o itemsmétodo não retorna mais uma lista , mas uma exibição , que age como um conjunto. Nesse caso, você precisará fazer a união do conjunto, pois concatenar com +não funcionará:

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

Para um comportamento semelhante ao python3 na versão 2.7, o viewitemsmétodo deve funcionar no lugar de items:

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

De qualquer forma, prefiro essa notação, pois parece mais natural pensar nela como uma operação de união de conjuntos, em vez de concatenação (como mostra o título).

Editar:

Mais alguns pontos para o python 3. Primeiro, observe que o dict(x, **y)truque não funcionará no python 3, a menos que as chaves ysejam cadeias de caracteres.

Além disso, a resposta do Chainmap de Raymond Hettinger é bastante elegante, pois pode usar um número arbitrário de dictes como argumentos, mas a partir dos documentos parece que ele sequencialmente examina uma lista de todos os dictos para cada pesquisa:

As pesquisas pesquisam os mapeamentos subjacentes sucessivamente até que uma chave seja encontrada.

Isso pode torná-lo mais lento se você tiver muitas pesquisas em seu aplicativo:

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

Portanto, uma ordem de magnitude mais lenta para pesquisas. Sou fã do Chainmap, mas parece menos prático onde pode haver muitas pesquisas.


22

Abuso que leva a uma solução de expressão única para a resposta de Mateus :

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

Você disse que queria uma expressão, então eu abusei lambdade vincular um nome e as tuplas substituem o limite de expressão única de lambda. Sinta-se livre para encolher.

Você também pode fazer isso, é claro, se não se importar em copiá-lo:

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

22

Solução simples usando ferramentas que preservam a ordem (os últimos dicionários têm precedência)

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

E é uso:

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


16

Embora as respostas sejam boas para este dicionário superficial , nenhum dos métodos definidos aqui realmente faz uma mesclagem profunda de dicionário.

Seguem exemplos:

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

Seria de esperar um resultado de algo assim:

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

Em vez disso, obtemos o seguinte:

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

A entrada 'one' deveria ter 'depth_2' e 'extra' como itens dentro de seu dicionário se realmente fosse uma mesclagem.

Usando cadeia também, não funciona:

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

Resulta em:

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

A fusão profunda que rcwesick deu também cria o mesmo resultado.

Sim, funcionará para mesclar os dicionários de amostra, mas nenhum deles é um mecanismo genérico para mesclar. Atualizarei isso mais tarde, quando escrever um método que faça uma verdadeira mesclagem.


11

(Somente para Python2.7 *; existem soluções mais simples para Python3 *.)

Se você não tem aversão à importação de um módulo de biblioteca padrão, pode fazer

from functools import reduce

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

(O or abit no lambdaé necessário porque dict.updatesempre retorna Nonecom sucesso.)


11

Se você não se importa de mudar x,

x.update(y) or x

Simples, legível, com bom desempenho. Você sabe update() sempre retorna None, que é um valor falso. Portanto, a expressão acima sempre será avaliada como xapós a atualização.

Os métodos de mutação na biblioteca padrão (como .update()) retornam Nonepor convenção, portanto esse padrão também funcionará nesses. Se você estiver usando um método que não segue esta convenção, ortalvez não funcione. Mas você pode usar uma exibição e índice de tupla para torná-lo uma única expressão. Isso funciona independentemente do que o primeiro elemento avalia.

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

Se você ainda não possui xuma variável, pode lambdacriar um local sem usar uma declaração de atribuição. Isso equivale a usar lambdacomo expressão let , que é uma técnica comum em linguagens funcionais, mas talvez não sintética.

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

Embora não seja tão diferente do uso a seguir do novo operador de morsa (somente Python 3.8 ou superior):

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

Se você deseja uma cópia, o estilo PEP 448 é mais fácil {**x, **y}. Mas se isso não estiver disponível na sua versão (mais antiga) do Python, o padrão let também funcionará aqui.

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

(Isso é, obviamente, equivalente a (z := x.copy()).update(y) or z, mas se a sua versão do Python for nova o suficiente para isso, o estilo PEP 448 estará disponível.)


10

Com base em idéias aqui e em outros lugares, compreendi uma função:

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

Uso (testado em 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})

Você poderia usar um lambda.


10

O problema que tenho com as soluções listadas até o momento é que, no dicionário mesclado, o valor da chave "b" é 10, mas, na minha opinião, deve ser 12. Nesse ponto, apresento o seguinte:

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

Resultados:

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}

1
Você pode estar interessado em cytoolz.merge_with( toolz.readthedocs.io/en/latest/… )
bli

10

É tão bobo que .updatenão retorna nada.
Eu apenas uso uma função auxiliar simples para resolver o problema:

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

Exemplos:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy

10
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

Isso deve resolver seu problema.


9

Isso pode ser feito com uma única compreensão de ditado:

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

Na minha opinião, a melhor resposta para a parte de "expressão única", pois não são necessárias funções extras, e é curta.


Eu suspeito que o desempenho não será muito bom; a criação de um conjunto de cada Dict só então a iteração através dos meios chaves outra pesquisa para o valor de cada tempo (apesar de relativamente rápido, ainda aumenta a fim da função de escalonamento)
Breezer

2
tudo depende da versão do python que estamos usando. Em 3.5 e acima, {** x, ** y} fornece o dicionário concatenado
Rashid Mv

9

Haverá uma nova opção quando o Python 3.8 for lançado ( programado para 20 de outubro de 2019 ), graças ao PEP 572: Assignment Expressions . O novo operador de expressão de atribuição :=permite atribuir o resultado do copye ainda usá-lo para chamar update, deixando o código combinado em uma única expressão, em vez de duas instruções, alterando:

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

para:

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

enquanto se comporta de maneira idêntica em todos os aspectos. Se você também deve retornar o resultado dict(você solicitou uma expressão retornando o dict; o acima cria e atribui a newdict, mas não a retorna, portanto, você não pode usá-la para passar um argumento para uma função como está, à la myfunc((newdict := dict1.copy()).update(dict2))) , adicione apenas or newdictaté o final (como updateretorna None, que é falso, ele avaliará e retornará newdictcomo resultado da expressão):

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

Advertência importante: Em geral, desencorajaria essa abordagem em favor de:

newdict = {**dict1, **dict2}

A abordagem de descompactação é mais clara (para quem sabe sobre descompactação generalizada em primeiro lugar, o que você deveria ), não exige um nome para o resultado (portanto, é muito mais conciso ao construir um temporário que é imediatamente passado para um ou incluída em um list/ tupleliteral ou similar) e é quase certamente mais rápida, sendo (no CPython) aproximadamente equivalente a:

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

mas feito na camada C, usando a dictAPI concreta , portanto, nenhuma sobrecarga de pesquisa / ligação de método dinâmico ou sobrecarga de despacho de chamada de função está envolvida (onde (newdict := dict1.copy()).update(dict2)inevitavelmente é idêntico ao comportamento de duas linhas original, executando o trabalho em etapas discretas, com pesquisa dinâmica / ligação / invocação de métodos.

Também é mais extensível, pois a fusão de três dicts é óbvia:

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

onde o uso de expressões de atribuição não será escalado dessa maneira; o mais próximo que você poderia chegar seria:

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

ou sem a tupla temporária de Nones, mas com o teste de veracidade de cada Noneresultado:

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

é obviamente muito mais feio e inclui outras ineficiências (um desperdício temporário tuplede Nones para separação por vírgula ou teste de veracidade inútil do retorno de cada updateum Nonepara a orseparação).

A única vantagem real da abordagem de expressão de atribuição ocorre se:

  1. Você tem código genérico que precisa lidar com sets e dicts (ambos suportam copye update, portanto, o código funciona da maneira que você espera)
  2. Você espera receber objetos arbitrários do tipo ditado , não apenas dictele próprio, e deve preservar o tipo e a semântica do lado esquerdo (em vez de terminar com um simples dict). Embora myspecialdict({**speciala, **specialb})possa funcionar, isso envolveria um temporário extra dicte, se myspecialdictpossui recursos simples, dictnão pode preservar (por exemplo, dicts regulares agora preservam a ordem com base na primeira aparência de uma chave e o valor com base na última aparência de uma chave; você pode querer aquele que preserva a ordem com base no último aparência de uma chave, para que a atualização de um valor também a mova para o final), então a semântica estaria errada. Como a versão da expressão de atribuição usa os métodos nomeados (que provavelmente são sobrecarregados para se comportarem adequadamente), ela nunca cria umdictde qualquer maneira (a menos que dict1já tenha sido a dict), preservando o tipo original (e a semântica do tipo original), evitando todos os temporários.

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}

Este método substitui xcom sua cópia. Se xé um argumento da função isto não vai funcionar (ver exemplo )
bartolo-otrit
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.