Maneira rápida de copiar dicionário em Python


92

Eu tenho um programa Python que funciona muito com dicionários. Tenho que fazer cópias de dicionários milhares de vezes. Preciso de uma cópia das chaves e do conteúdo associado. A cópia será editada e não deve ser vinculada ao original (por exemplo, as alterações na cópia não devem afetar o original).

As chaves são strings, os valores são inteiros (0/1).

Atualmente, uso uma maneira simples:

newDict = oldDict.copy()

A criação de perfil do meu código mostra que a operação de cópia leva a maior parte do tempo.

Existem alternativas mais rápidas para o dict.copy()método? O que seria mais rápido?


1
Se o valor puder ser 0 ou 1, seria uma boolescolha melhor do que int?
Samir Talwar

5
E se você precisar de milhares de cópias deles, as máscaras de bits funcionariam ainda melhor?
Wooble

@Samir não está boolem Python nomeado de intqualquer maneira.
Santa

Eu concordo, porém, que um bitmask pode ser mais eficiente para você (dependendo de como você usa esse "dict", na verdade).
Santa

1
Para esclarecer, o booltipo é na verdade uma subclasse (subtipo?) Do inttipo.
Santa

Respostas:


64

Olhando o código-fonte C para as dictoperações Python , você pode ver que eles fazem uma cópia bastante ingênua (mas eficiente). Essencialmente se resume a uma chamada para PyDict_Merge:

PyDict_Merge(PyObject *a, PyObject *b, int override)

Isso faz verificações rápidas de coisas como se eles são o mesmo objeto e se eles contêm objetos. Depois disso, ele faz um redimensionamento / alocação único generoso para o dicionário de destino e, em seguida, copia os elementos um por um. Não vejo você ficando muito mais rápido do que o embutido copy().


1
Parece que é melhor reescrever o código para evitar o uso de dictos - ou usar uma estrutura de dados mais rápida que pode fazer o mesmo trabalho. Muito obrigado pela resposta!
Joern

56

Aparentemente, o dict.copy é mais rápido, como você diz.

[utdmr@utdmr-arch ~]$ python -m timeit -s "d={1:1, 2:2, 3:3}" "new = d.copy()"
1000000 loops, best of 3: 0.238 usec per loop
[utdmr@utdmr-arch ~]$ python -m timeit -s "d={1:1, 2:2, 3:3}" "new = dict(d)"
1000000 loops, best of 3: 0.621 usec per loop
[utdmr@utdmr-arch ~]$ python -m timeit -s "from copy import copy; d={1:1, 2:2, 3:3}" "new = copy(d)"
1000000 loops, best of 3: 1.58 usec per loop

Obrigado pela comparação! Tentaremos reescrever o código para evitar o uso de cópia de ditado na maioria dos lugares. Obrigado novamente!
Joern

4
A maneira de fazer a última comparação sem contar o custo de fazer a importação cada vez é com timeit's -sargumento: python -m timeit -s "from copy import copy" "new = copy({1:1, 2:2, 3:3})". Enquanto você faz isso, retire a criação do dicionário também (para todos os exemplos).
Thomas Wouters

Talvez seja melhor repetir os processos muitas vezes, pois pode haver algumas flutuações em uma cena específica.
xiaohan2012

2
Timeit faz isso; como ele diz, faz loops 1.000.000 de vezes e calcula a média.
utdemir

Eu tenho horários conflitantes. a = {b: b para b no intervalo (10000)} Em [5]:% timeit copy (a) 10000 loops, melhor de 3: 186 µs por loop In [6]:% timeit deepcopy (a) 100 loops, melhor de 3: 14,1 ms por loop Em [7]:% timeit a.copy () 1000 loops, melhor de 3: 180 µs por loop
Davoud Taghawi-Nejad de

12

Você pode fornecer um exemplo de código para que eu possa ver como você está usando copy () e em que contexto?

Você poderia usar

new = dict(old)

Mas não acho que será mais rápido.


5

Sei que esse é um tópico antigo, mas é um resultado alto em mecanismos de pesquisa para "dict copy python" e o resultado principal para "dict copy performance", e acredito que isso seja relevante.

Do Python 3.7, newDict = oldDict.copy()é até 5,5 vezes mais rápido do que era anteriormente. Notavelmente, agora,newDict = dict(oldDict) não parece ter esse aumento de desempenho.

Há um pouco mais de informações aqui .


3

Dependendo das coisas que você deixa para especulação, você pode embrulhar o dicionário original e fazer uma espécie de cópia na escrita.

A "cópia" é então um dicionário que procura coisas no dicionário "pai", se ainda não contém a chave --- mas armazena modificações em si mesmo.

Isso pressupõe que você não modificará o original e que as pesquisas extras não custarão mais.


2

No entanto, as medições dependem do tamanho do dicionário. Para 10.000 entradas, copy (d) e d.copy () são quase iguais.

a = {b: b for b in range(10000)} 
In [5]: %timeit copy(a)
10000 loops, best of 3: 186 µs per loop
In [6]: %timeit deepcopy(a)
100 loops, best of 3: 14.1 ms per loop
In [7]: %timeit a.copy()
1000 loops, best of 3: 180 µs per loop
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.