Como faço para mesclar dicionários em Python?

90
d3 = dict(d1, **d2)

Eu entendo que isso mescla o dicionário. Mas, é único? E se d1 tiver a mesma chave que d2, mas um valor diferente? Gostaria que d1 e d2 fossem mesclados, mas d1 tem prioridade se houver chave duplicada.

TIMEX
fonte
9
Esteja ciente de que este truque é considerado um abuso de **passagem de argumento de palavra-chave, a menos que todas as chaves de d2sejam strings. Se nem todas as chaves d2são strings, isso falha no Python 3.2 e em implementações alternativas de Python como Jython, IronPython e PyPy. Consulte, por exemplo, mail.python.org/pipermail/python-dev/2010-April/099459.html .
Mark Dickinson

Respostas:

151

Você pode usar o .update()método se não precisar mais do original d2:

Atualize o dicionário com os pares de chave / valor de outro, sobrescrevendo as chaves existentes . Retorno None.

Por exemplo:

>>> d1 = {'a': 1, 'b': 2} 
>>> d2 = {'b': 1, 'c': 3}
>>> d2.update(d1)
>>> d2
{'a': 1, 'c': 3, 'b': 2}

Atualizar:

Claro que você pode copiar o dicionário primeiro para criar um novo mesclado. Isso pode ou não ser necessário. Caso você tenha objetos compostos (objetos que contêm outros objetos, como listas ou instâncias de classe) em seu dicionário, copy.deepcopytambém deve ser considerado.

Felix Kling
fonte
1
Com este caso, os elementos d1 devem receber prioridade corretamente se chaves conflitantes forem encontradas
Trey Hunner
Caso ainda precise, basta fazer uma cópia. d3 = d2.copy () d3.update (d1) mas gostaria de ver d1 + d2 sendo adicionado ao idioma.
stach
4
d1 + d2 é problemático porque um dicionário deve ter prioridade durante os conflitos, e não é particularmente óbvio qual deles.
rjh
d1 + d2 só será implementado se Python obtiver um multimap, caso contrário, a ambigüidade para o usuário é muito confusa para o ganho de digitação de 8 bytes.
Nick Bastin
Você tem objetos no dicionário neste exemplo: isinstance(int, object) is Trueainda deepcopynão parece necessário.
Antony Hatchkins
43

Em Python2,

d1={'a':1,'b':2}
d2={'a':10,'c':3}

d1 substitui d2:

dict(d2,**d1)
# {'a': 1, 'c': 3, 'b': 2}

d2 substitui d1:

dict(d1,**d2)
# {'a': 10, 'c': 3, 'b': 2}

Esse comportamento não é apenas um acaso de implementação; é garantido na documentação :

Se uma chave for especificada no argumento posicional e como um argumento de palavra-chave, o valor associado à palavra-chave será retido no dicionário.

unutbu
fonte
3
Seus exemplos falharão (produzindo um TypeError) no Python 3.2 e nas versões atuais do Jython, PyPy e IronPython: para essas versões do Python, ao passar um dict com a **notação, todas as chaves desse dict devem ser strings. Consulte o thread python-dev começando em mail.python.org/pipermail/python-dev/2010-April/099427.html para mais.
Mark Dickinson
@Mark: Obrigado pelo aviso. Eu editei o código para torná-lo compatível com implementações não CPython.
unutbu
3
ele falhará se suas chaves forem tuplas de strings e números. por exemplo. d1 = {(1, 'a'): 1, (1, 'b'): 0,} d2 = {(1, 'a'): 1, (2, 'b'): 2, (2, 'a'): 1,}
MySchizoBuddy
Com relação à sintaxe de descompactação, consulte esta postagem para as mudanças futuras no python 3.5.
Ioannis Filippidis
Eu ia dizer que d = dict(**d1, **d2)funciona, mas é o que @IoannisFilippidis faz referência em seu comentário. Talvez incluir o snippet aqui fosse mais claro, então aqui está.
dwanderson
14

Se você quiser d1ter prioridade nos conflitos, faça:

d3 = d2.copy()
d3.update(d1)

Caso contrário, inverta d2e d1.

tzot
fonte
1

Minha solução é definir uma função de mesclagem . Não é sofisticado e custa apenas uma linha. Aqui está o código em Python 3.

from functools import reduce
from operator import or_

def merge(*dicts):
    return { k: reduce(lambda d, x: x.get(k, d), dicts, None) for k in reduce(or_, map(lambda x: x.keys(), dicts), set()) }

Testes

>>> d = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> d_letters = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d, d_letters)
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d_letters, d)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> merge(d_letters)
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge()
{}

Ele funciona para um número arbitrário de argumentos do dicionário. Se houver alguma chave duplicada nesse dicionário, a chave do dicionário mais à direita na lista de argumentos vence.

Lei Zhao
fonte
1
Um loop simples com uma .updatechamada nele ( merged={}seguido por for d in dict: merged.update(d)) seria mais curto, mais legível e mais eficiente.
Mark Dickinson
1
Ou se você realmente quer usar reducee lambdaé, como sobre return reduce(lambda x, y: x.update(y) or x, dicts, {})?
Mark Dickinson
1
Você pode experimentar seu código no shell e ver se está correto. O que eu estava tentando fazer é escrever uma função que pode ter vários argumentos de dicionário com a mesma funcionalidade. É melhor não usar x.update (y) no lambda, porque ele sempre retorna None . E estou tentando escrever uma função merge_with mais geral que pegue vários números de argumentos de dicionário e lide com chaves duplicadas com a função fornecida. Assim que terminar, vou postar em outro tópico onde a solução for mais relevante.
Lei Zhao
Aqui está o link onde escrevi a solução mais geral. Bem-vindo e dê uma olhada.
Lei Zhao
1

Começando em Python 3.9, o operador |cria um novo dicionário com as chaves e valores mesclados de dois dicionários:

# d1 = { 'a': 1, 'b': 2 }
# d2 = { 'b': 1, 'c': 3 }
d3 = d2 | d1
# d3: {'b': 2, 'c': 3, 'a': 1}

Este:

Cria um novo dicionário d3 com as chaves e valores mesclados de d2 e d1. Os valores de d1 têm prioridade quando d2 e d1 compartilham chaves.


Observe também o |=operador que modifica d2 mesclando d1 em, com prioridade nos valores d1:

# d1 = { 'a': 1, 'b': 2 }
# d2 = { 'b': 1, 'c': 3 }
d2 |= d1
# d2: {'b': 2, 'c': 3, 'a': 1}

Xavier Guihot
fonte
0

Acredito que, conforme dito acima, usar d2.update(d1)é a melhor abordagem e que você também pode copiar d2primeiro se ainda precisar.

Embora eu queira ressaltar que, dict(d1, **d2)na verdade, é uma maneira ruim de mesclar dicionários em geral, já que os argumentos de palavra-chave precisam ser strings, portanto, falhará se você tiver um dict, como:

{
  1: 'foo',
  2: 'bar'
}
Olivier Melançon
fonte