Un moyen rapide de copier un dictionnaire en Python

92

J'ai un programme Python qui fonctionne beaucoup avec des dictionnaires. Je dois faire des copies de dictionnaires des milliers de fois. J'ai besoin d'une copie des clés et du contenu associé. La copie sera éditée et ne doit pas être liée à l'original (par exemple, les modifications apportées à la copie ne doivent pas affecter l'original.)

Les clés sont des chaînes, les valeurs sont des nombres entiers (0/1).

J'utilise actuellement un moyen simple:

newDict = oldDict.copy()

Le profilage de mon code montre que l'opération de copie prend la plupart du temps.

Existe-t-il des alternatives plus rapides à la dict.copy()méthode? Quel serait le plus rapide?

Joern
la source
1
Si la valeur peut être 0 ou 1, est-ce que a boolserait un meilleur choix que an int?
Samir Talwar
5
Et si vous en avez besoin de milliers de copies, les masques de bits fonctionneraient-ils encore mieux?
Wooble
@Samir n'est de toute façon pas boolnommé en Python int.
Santa
Je suis d'accord, cependant, qu'un masque de bits pourrait être plus efficace pour vous (en fonction de la façon dont vous utilisez ce "dict", vraiment).
Santa
1
Pour clarifier, le booltype est en fait une sous-classe (sous-type?) Du inttype.
Santa

Réponses:

64

En regardant la source C pour les dictopérations Python , vous pouvez voir qu'elles font une copie assez naïve (mais efficace). Cela se résume essentiellement à un appel à PyDict_Merge:

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

Cela vérifie rapidement des choses comme s'il s'agit du même objet et s'ils contiennent des objets. Après cela, il effectue un redimensionnement / allocation unique généreux au dict cible, puis copie les éléments un par un. Je ne vous vois pas devenir beaucoup plus rapide que le système intégré copy().

Daniel DiPaolo
la source
1
On dirait que je ferais mieux de réécrire le code pour éviter du tout l'utilisation de dictionnaires - ou d'utiliser une structure de données plus rapide qui peut faire le même travail. Merci beaucoup pour la réponse!
Joern
56

Apparemment, dict.copy est plus rapide, comme vous le dites.

[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
utdemir
la source
Merci pour la comparaison! Essayera de réécrire le code pour éviter l'utilisation de la copie de dict dans la plupart des endroits. Merci encore!
Joern
4
La façon de faire la dernière comparaison sans compter le coût de faire l'importation chaque fois est avec timeitde » -sargument: python -m timeit -s "from copy import copy" "new = copy({1:1, 2:2, 3:3})". Pendant que vous y êtes, retirez également la création de dict (pour tous les exemples.)
Thomas Wouters
Il est peut-être préférable de répéter les processus plusieurs fois car il peut y avoir des fluctuations d'un tir spécifique.
xiaohan2012
2
Timeit fait cela; comme il dit, il boucle 1000000 fois et en fait la moyenne.
utdemir
J'ai des horaires contradictoires. a = {b: b pour b in range (10000)} In [5]:% timeit copy (a) 10000 boucles, meilleur de 3: 186 µs par boucle In [6]:% timeit deepcopy (a) 100 boucles, meilleur de 3: 14,1 ms par boucle In [7]:% timeit a.copy () 1000 boucles, meilleur de 3: 180 µs par boucle
Davoud Taghawi-Nejad
12

Pouvez-vous fournir un exemple de code afin que je puisse voir comment vous utilisez copy () et dans quel contexte?

Vous pourriez utiliser

new = dict(old)

Mais je ne pense pas que ce sera plus rapide.

MikeVaughan
la source
5

Je me rends compte que c'est un vieux fil, mais c'est un résultat élevé dans les moteurs de recherche pour "dict copy python", et le meilleur résultat pour "dict copy performance", et je pense que c'est pertinent.

Depuis Python 3.7, il newDict = oldDict.copy()est jusqu'à 5,5 fois plus rapide qu'auparavant. Notamment, en ce moment,newDict = dict(oldDict) ne semble pas avoir cette augmentation des performances.

Il y a un peu plus d'informations ici .

iandioch
la source
3

Selon ce que vous laissez à la spéculation, vous voudrez peut-être envelopper le dictionnaire original et faire une sorte de copie sur écriture.

La "copie" est alors un dictionnaire qui recherche des trucs dans le dictionnaire "parent", s'il ne contient pas déjà la clé --- mais bourre les modifications en lui-même.

Cela suppose que vous ne modifierez pas l'original et que les recherches supplémentaires ne coûteront pas plus cher.

Alex Brasetvik
la source
2

Les mesures dépendent cependant de la taille du dictionnaire. Pour 10000 entrées, copy (d) et d.copy () sont presque identiques.

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
Davoud Taghawi-Nejad
la source