Быстрый способ скопировать словарь в Python

92

У меня есть программа на Python, которая много работает со словарями. Мне приходится копировать словари тысячи раз. Мне нужна копия ключей и связанного с ними содержимого. Копия будет отредактирована и не должна быть связана с оригиналом (например, изменения в копии не должны влиять на оригинал).

Ключи - это строки, значения - целые (0/1).

Сейчас я использую простой способ:

newDict = oldDict.copy()

Профилирование моего кода показывает, что операция копирования занимает большую часть времени.

Есть ли более быстрые альтернативы этому dict.copy()методу? Что было бы быстрее всего?

Joern
источник
1
Если значение может быть либо 0, либо 1, что было boolбы лучше, чем int?
Самир Талвар
5
И если вам нужны тысячи их копий, будут ли битовые маски работать еще лучше?
Wooble
@Samir все равно не boolназван в Python int.
Санта
Я согласен, однако, с тем, что битовая маска может быть более эффективной для вас (на самом деле, в зависимости от того, как вы используете этот «диктант»).
Санта
1
Чтобы уточнить, boolтип на самом деле является подклассом (подтипом?) intТипа.
Санта

Ответы:

64

Посмотрев на исходный кодdict операций Python на C , вы увидите, что они делают довольно наивную (но эффективную) копию. По сути, это сводится к призыву PyDict_Merge:

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

Это позволяет быстро проверять, являются ли они одним и тем же объектом и есть ли в них объекты. После этого он выполняет одноразовое щедрое изменение размера / выделения для целевого dict, а затем копирует элементы один за другим. Я не вижу, чтобы вы стали намного быстрее, чем встроенный copy().

Даниэль ДиПаоло
источник
1
Похоже, мне лучше переписать код, чтобы вообще избежать использования dicts - или использовать более быструю структуру данных, которая может выполнять ту же работу. Большое спасибо за ответ!
Joern
56

По-видимому, dict.copy, как вы говорите, быстрее.

[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
утдемир
источник
Спасибо за сравнение! Постараюсь переписать код, чтобы избежать использования dict-копирования в большинстве мест. Еще раз спасибо!
Joern
4
Способ сделать последнее сравнение без учета стоимости делать импорт каждый раз , когда есть с timeit«S -sаргумент: python -m timeit -s "from copy import copy" "new = copy({1:1, 2:2, 3:3})". Пока вы занимаетесь этим, вытащите также создание dict (для всех примеров)
Томас Воутерс,
Может быть, лучше повторить процессы много раз, так как могут быть некоторые колебания одного конкретного кадра.
xiaohan2012
2
Timeit это делает; как говорится, он повторяется 1000000 раз и усредняет его.
utdemir
У меня противоречивые сроки. a = {b: b для b в диапазоне (10000)} В [5]:% timeit copy (a) 10000 циклов, лучшее из 3: 186 мкс на цикл В [6]:% timeit deepcopy (a) 100 циклов, лучшее из 3: 14,1 мс на цикл В [7]:% timeit a.copy () 1000 циклов, лучшее из 3: 180 мкс на цикл
Давуд Тагави-Нежад
12

Не могли бы вы предоставить образец кода, чтобы я мог увидеть, как вы используете copy () и в каком контексте?

Вы могли бы использовать

new = dict(old)

Но не думаю, что это будет быстрее.

Майк Воган
источник
5

Я понимаю, что это старый поток, но это высокий результат в поисковых системах для «dict copy python» и лучший результат для «dict copy performance», и я считаю, что это актуально.

Начиная с Python 3.7, newDict = oldDict.copy()он стал до 5,5 раз быстрее, чем был раньше. Примечательно, что прямо сейчасnewDict = dict(oldDict) похоже, не наблюдается увеличения производительности.

Существует немного больше информации здесь .

иандиох
источник
3

В зависимости от того, что вы оставите на размышления, вы можете обернуть исходный словарь и сделать что- то вроде копирование при записи.

«Копия» - это тогда словарь, который ищет материал в «родительском» словаре, если он еще не содержит ключа, но вносит изменения в себя.

Это предполагает, что вы не будете изменять оригинал и что дополнительные поиски не будут стоить дороже.

Алекс Брасетвик
источник
2

Однако измерения зависят от размера словаря. Для 10000 записей copy (d) и d.copy () почти одинаковы.

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
Давуд Тагави-Нежад
источник