Szybki sposób na skopiowanie słownika w Pythonie

92

Mam program w języku Python, który często współpracuje ze słownikami. Muszę robić kopie słowników tysiące razy. Potrzebuję kopii kluczy i związanej z nimi zawartości. Kopia zostanie poddana edycji i nie może być powiązana z oryginałem (np. Zmiany w kopii nie mogą wpływać na oryginał).

Klucze to ciągi, wartości to liczby całkowite (0/1).

Obecnie używam prostego sposobu:

newDict = oldDict.copy()

Profilowanie mojego kodu pokazuje, że operacja kopiowania zajmuje większość czasu.

Czy istnieją szybsze alternatywy dla tej dict.copy()metody? Co byłoby najszybsze?

Joern
źródło
1
Jeśli wartość może wynosić 0 lub 1, czy a boolbyłby lepszym wyborem niż int?
Samir Talwar
5
A gdybyś potrzebował tysięcy ich kopii, czy maski bitowe działałyby jeszcze lepiej?
Wooble
@Samir i tak nie ma nazwy boolw Pythonie int.
Święty Mikołaj
Zgadzam się jednak, że maska ​​bitowa może być dla Ciebie bardziej wydajna (w zależności od tego, jak używasz tego „dyktu”).
Święty Mikołaj
1
Aby wyjaśnić, booltyp jest w rzeczywistości podklasą (podtypem?) intTypu.
Święty Mikołaj

Odpowiedzi:

64

Patrząc na źródło C dla dictoperacji Pythona , widać, że wykonują one dość naiwną (ale wydajną) kopię. Zasadniczo sprowadza się do wezwania do PyDict_Merge:

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

To wykonuje szybkie sprawdzenie rzeczy, takich jak to, czy są tym samym obiektem i czy mają w sobie obiekty. Następnie wykonuje jednorazową hojną zmianę rozmiaru / alokację do docelowego dyktu, a następnie kopiuje elementy jeden po drugim. Nie widzę, żebyś był dużo szybszy niż wbudowany copy().

Daniel DiPaolo
źródło
1
Wygląda na to, że lepiej przepiszę kod, aby w ogóle uniknąć używania poleceń - lub użyć szybszej struktury danych, która może wykonać to samo zadanie. Bardzo dziękuję za odpowiedź!
Joern
56

Jak mówisz, najwyraźniej dict.copy jest szybsze.

[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
źródło
Dzięki za porównanie! Spróbuje przepisać kod, aby w większości miejsc uniknąć kopiowania dyktowania. Dzięki jeszcze raz!
Joern
4
Sposobem na to ostatnie porównanie nie licząc kosztów prowadzenia importu za każdym razem jest z timeit„s -sargumentu: python -m timeit -s "from copy import copy" "new = copy({1:1, 2:2, 3:3})". Kiedy już to zrobisz, wyciągnij również dyktando (dla wszystkich przykładów).
Thomas Wouters
Może powtarzanie tych procesów wiele razy jest lepsze, ponieważ mogą wystąpić pewne wahania jednego konkretnego ujęcia.
xiaohan2012
2
Czas to robi; jak mówi, wykonuje pętlę 1000000 razy i uśrednia ją.
utdemir
Mam sprzeczne czasy. a = {b: b for b in range (10000)} W [5]:% timeit copy (a) 10000 pętli, najlepsza z 3: 186 µs na pętlę In [6]:% timeit deepcopy (a) 100 pętli, best of 3: 14,1 ms na pętlę In [7]:% timeit a.copy () 1000 pętli, najlepszy z 3: 180 µs na pętlę
Davoud Taghawi-Nejad
12

Czy możesz podać przykładowy kod, abym mógł zobaczyć, jak używasz funkcji copy () i w jakim kontekście?

Możesz użyć

new = dict(old)

Ale nie sądzę, żeby to było szybsze.

MikeVaughan
źródło
5

Zdaję sobie sprawę, że to stary wątek, ale jest to wysoki wynik w wyszukiwarkach dla hasła „dict copy python” i najwyższy wynik dla „wydajności kopiowania dyktowania” i uważam, że to ma znaczenie.

Od wersji Python 3.7 newDict = oldDict.copy()jest nawet 5,5x szybsza niż poprzednio. Warto zauważyć, że w tej chwili newDict = dict(oldDict)wydaje się , że nie ma takiego wzrostu wydajności.

Jest trochę więcej informacji tutaj .

iandioch
źródło
3

W zależności od rzeczy, które pozostawisz spekulacjom, możesz chcieć zawinąć oryginalny słownik i zrobić coś w rodzaju kopiowania przy zapisie.

„Kopia” jest wówczas słownikiem, który wyszukuje rzeczy w słowniku „nadrzędnym”, jeśli nie zawiera już klucza --- ale umieszcza modyfikacje w sobie.

Zakłada się, że nie będziesz modyfikować oryginału i że dodatkowe wyszukiwania nie będą kosztować więcej.

Alex Brasetvik
źródło
2

Pomiary zależą jednak od rozmiaru słownika. Dla 10000 wpisów copy (d) i d.copy () są prawie takie same.

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
źródło