Co to są obiekty widoku słownika?


158

W Pythonie 2.7 mamy dostępne metody widoku słownika .

Teraz znam zalety i wady następujących rzeczy:

  • dict.items()(i values, keys): zwraca listę, więc możesz faktycznie zapisać wynik, a
  • dict.iteritems() (i tym podobne): zwraca generator, więc możesz iterować po każdej wygenerowanej wartości.

Po co dict.viewitems()(i tym podobne)? Jakie są ich zalety? Jak to działa? Czym w końcu jest widok?

Czytałem, że widok zawsze odzwierciedla zmiany ze słownika. Ale jak to się zachowuje z punktu widzenia perfekcji i pamięci? Jakie są zalety i wady?

Odpowiedzi:


157

Widoki słownika są zasadniczo tym, co mówi ich nazwa: widoki są po prostu jak okno na klucze i wartości (lub elementy) słownika. Oto fragment oficjalnej dokumentacji dla Pythona 3:

>>> dishes = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500}
>>> keys = dishes.keys()
>>> values = dishes.values()

>>> # view objects are dynamic and reflect dict changes
>>> del dishes['eggs']
>>> keys  # No eggs anymore!
dict_keys(['sausage', 'bacon', 'spam'])

>>> values  # No eggs value (2) anymore!
dict_values([1, 1, 500])

(Odpowiednik Pythona 2 używa dishes.viewkeys()i dishes.viewvalues().)

Ten przykład pokazuje dynamiczny charakter widoków : widok kluczy nie jest kopią kluczy w danym momencie, ale raczej prostym oknem, które pokazuje klucze; jeśli ulegną zmianie, zmieni się również to, co widzisz przez okno. Ta funkcja może być przydatna w pewnych okolicznościach (na przykład można pracować z widokiem kluczy w wielu częściach programu zamiast przeliczać bieżącą listę kluczy za każdym razem, gdy są potrzebne) - należy zauważyć, że jeśli klucze w słowniku zostaną zmodyfikowane podczas iterowania widoku, sposób, w jaki iterator powinien zachowywać się, nie jest dobrze zdefiniowany, co może prowadzić do błędów .

Zaletą jest to, że patrząc , powiedzmy, klucze używają tylko małej i stałej ilości pamięci i wymagają małego i stałego czasu procesora , ponieważ nie ma tworzenia listy kluczy (z drugiej strony Python 2, często niepotrzebnie tworzy nową listę, jak cytuje Rajendran T, która zajmuje pamięć i czas w ilości proporcjonalnej do długości listy). Kontynuując analogię z oknem, jeśli chcesz zobaczyć krajobraz za ścianą, po prostu zrobisz w nim otwór (zbudujesz okno); skopiowanie kluczy do listy oznaczałoby zamiast tego pomalowanie kopii krajobrazu na ścianie - kopia wymaga czasu, miejsca i nie aktualizuje się sama.

Podsumowując, widoki to po prostu… widoki (okna) w Twoim słowniku, które pokazują zawartość słownika nawet po zmianie. Oferują funkcje, które różnią się od tych z list: lista kluczy zawiera kopię kluczy słownika w danym momencie, podczas gdy widok jest dynamiczny i znacznie szybszy do uzyskania, ponieważ nie musi kopiować żadnych danych ( klucze lub wartości) w celu utworzenia.


6
+1. Ok, czym się to różni od bezpośredniego dostępu do wewnętrznej listy kluczy? Czy to szybciej, wolniej? Większa wydajność pamięci? Zastrzeżony? Jeśli możesz ją czytać i edytować, czujesz się dokładnie tak samo, jak posiadanie odniesienia do tej listy.
e-satis

3
Dzięki. Chodzi o to, że widoki twoim dostępem do "wewnętrznej listy kluczy" (zauważ, że ta "lista kluczy" nie jest jednak listą Pythona, ale jest dokładnie widokiem). Widoki są bardziej wydajne w pamięci niż listy kluczy (lub wartości lub elementów) w Pythonie 2, ponieważ niczego nie kopiują; faktycznie przypominają „odniesienie do listy kluczy” (zwróć uwagę również, że „odniesienie do listy” jest w Pythonie po prostu nazywane listą, ponieważ listy są obiektami zmiennymi). Pamiętaj również, że nie możesz bezpośrednio edytować widoków: zamiast tego nadal edytujesz słownik, a widoki natychmiast odzwierciedlają wprowadzone zmiany.
Eric O Lebigot

3
Ok, nie mam jeszcze jasności co do implementacji, ale jak dotąd to najlepsza odpowiedź.
e-satis

2
Dzięki. Rzeczywiście, ta odpowiedź dotyczy głównie semantyki poglądów. Nie mam informacji o ich implementacji w CPythonie, ale domyślam się, że widok jest w zasadzie wskaźnikiem do odpowiednich struktur (kluczy i / lub wartości), a struktury te są częścią samego obiektu słownika.
Eric O Lebigot

5
Myślę, że warto zwrócić uwagę, że przykładowy kod w tym poście pochodzi z python3 i nie jest tym, co otrzymuję w python2.7.
snth

21

Jak wspomniałeś, dict.items()zwraca kopię listy par (klucz, wartość) słownika, co jest marnotrawstwem i dict.iteritems()zwraca iterator po parach słownika (klucz, wartość).

Teraz weźmy następujący przykład, aby zobaczyć różnicę między interatorem dyktowania a poglądem dyktowania

>>> d = {"x":5, "y":3}
>>> iter = d.iteritems()
>>> del d["x"]
>>> for i in iter: print i
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

Natomiast widok po prostu pokazuje, co jest w dyktandzie. Nie obchodzi go, czy się zmieniło:

>>> d = {"x":5, "y":3}
>>> v = d.viewitems()
>>> v
dict_items([('y', 3), ('x', 5)])
>>> del d["x"]
>>> v
dict_items([('y', 3)])

Widok jest po prostu tym, jak teraz wygląda słownik. Po usunięciu wpis .items()byłby nieaktualny i .iteritems()spowodowałby błąd.


Świetny przykład, dzięki. Jednak, powinno być v = d.items () nie V - d.viewitems ()
Rix

1
Pytanie dotyczy Pythona 2.7, więc viewitems()faktycznie jest poprawne ( items()poprawnie daje widok w Pythonie 3 ).
Eric O Lebigot

Jednak widoku nie można używać do iteracji po słowniku podczas jego modyfikowania.
Ioannis Filippidis

18

Po przeczytaniu dokumentów mam takie wrażenie:

  1. Widoki są „pseudo-zestawami”, ponieważ nie obsługują indeksowania, więc co możesz z nimi zrobić, to przetestować członkostwo i iterować po nich (ponieważ klucze są haszowalne i unikalne, klucze i widoki elementów są bardziej " set-like ”, ponieważ nie zawierają duplikatów).
  2. Możesz je przechowywać i używać wiele razy, tak jak wersje list.
  3. Ponieważ odzwierciedlają podstawowy słownik, każda zmiana w słowniku zmieni widok i prawie na pewno zmieni kolejność iteracji . W przeciwieństwie do wersji list, nie są one „stabilne”.
  4. Ponieważ odzwierciedlają podstawowy słownik, prawie na pewno są małymi obiektami proxy; kopiowanie kluczy / wartości / elementów wymagałoby, aby w jakiś sposób oglądali oryginalny słownik i kopiowali go wielokrotnie, gdy zajdą zmiany, co byłoby absurdalną implementacją. Spodziewałbym się więc bardzo małego narzutu pamięci, ale dostęp byłby trochę wolniejszy niż bezpośrednio do słownika.

Wydaje mi się, że kluczowym przypadkiem użycia jest przechowywanie słownika i wielokrotne iterowanie jego kluczy / elementów / wartości z modyfikacjami pomiędzy nimi. Zamiast tego możesz po prostu użyć widoku, zamieniając się for k, v in mydict.iteritems():w for k, v in myview:. Ale jeśli tylko raz przeglądasz słownik, myślę, że wersje iterowe są nadal lepsze.


2
+1 za analizę zalet i wad z kilku otrzymanych informacji.
e-satis

Jeśli utworzę iterator dla widoku, nadal będzie on unieważniany po każdej zmianie słownika. To ten sam problem, co w przypadku iteratora po samym słowniku (np iteritems().). Jaki jest więc sens tych poglądów? Kiedy się cieszę, że je mam?
Alfe

@Alfe Masz rację, to jest problem z iteracją słownika, a widoki w ogóle mu nie pomagają. Powiedzmy, że musisz przekazać wartości ze słownika do funkcji. Możesz użyć .values(), ale wymaga to zrobienia całej kopii jako listy, co może być kosztowne. Jest, .itervalues()ale nie możesz ich konsumować więcej niż raz, więc nie będzie działać z każdą funkcją. Widoki nie wymagają kosztownej kopii, ale nadal są bardziej przydatne jako samodzielna wartość niż iterator. Ale nadal nie są przeznaczone do pomocy w iterowaniu i modyfikowaniu w tym samym czasie (tam naprawdę potrzebujesz kopii).
Ben

17

Metody zobacz zwróci listę (nie kopię listy, w porównaniu do .keys(), .items()i .values()), więc jest bardziej lekkie, ale odzwierciedla bieżącą zawartość słownika.

Z Pythona 3.0 - metody dict zwracają widoki - dlaczego?

Głównym powodem jest to, że w wielu przypadkach zwracanie całkowicie odłączonej listy jest niepotrzebne i niepotrzebne. Wymagałoby to skopiowania całej treści (co może być dużo lub niewiele).

Jeśli chcesz po prostu iterować po kluczach, tworzenie nowej listy nie jest konieczne. A jeśli rzeczywiście potrzebujesz jej jako oddzielnej listy (jako kopii), możesz łatwo utworzyć tę listę z widoku.


6
Metody widoku zwracają obiekty widoku, które nie są zgodne z interfejsem listy.
Matthew Trevor

5

Widoki umożliwiają dostęp do podkładanej struktury danych bez jej kopiowania. Oprócz tego, że są dynamiczne, a nie tworzą listy, jednym z ich najbardziej przydatnych zastosowań jest intest. Załóżmy, że chcesz sprawdzić, czy wartość jest w dyktacie, czy nie (jest to klucz lub wartość).

Pierwszą opcją jest utworzenie listy kluczy za pomocą dict.keys(), to działa, ale oczywiście zużywa więcej pamięci. Jeśli dyktando jest bardzo duże? Byłoby to marnotrawstwem.

Dzięki temu viewsmożesz iterować rzeczywistą strukturę danych, bez listy pośredniej.

Posłużmy się przykładami. Mam dykt z 1000 kluczy losowych ciągów i cyfr i kjest kluczem, którego chcę szukać

large_d = { .. 'NBBDC': '0RMLH', 'E01AS': 'UAZIQ', 'G0SSL': '6117Y', 'LYBZ7': 'VC8JQ' .. }

>>> len(large_d)
1000

# this is one option; It creates the keys() list every time, it's here just for the example
timeit.timeit('k in large_d.keys()', setup='from __main__ import large_d, k', number=1000000)
13.748743600954867


# now let's create the list first; only then check for containment
>>> list_keys = large_d.keys()
>>> timeit.timeit('k in list_keys', setup='from __main__ import large_d, k, list_keys', number=1000000)
8.874809793833492


# this saves us ~5 seconds. Great!
# let's try the views now
>>> timeit.timeit('k in large_d.viewkeys()', setup='from __main__ import large_d, k', number=1000000)
0.08828549011070663

# How about saving another 8.5 seconds?

Jak widać, iteracja viewobiektu daje ogromny wzrost wydajności, jednocześnie zmniejszając obciążenie pamięci. Powinieneś ich używać, gdy musisz wykonać Setpodobne operacje.

Uwaga : korzystam z Pythona 2.7


W pythonie> = 3 uważam, że .keys()domyślnie zwraca widok. Mógłbym chcieć podwójnie sprawdzić
Yolo Voe

1
Masz rację. Python 3+ intensywnie korzysta z obiektów widoku zamiast list, jest znacznie bardziej wydajny w pamięci
Chen A.

1
Te wyniki czasowe są bardzo wymowne, ale sprawdzanie, czy kjest jednym z kluczy słownika, large_djest przeznaczone do wykonania k in large_dw Pythonie, który jest prawdopodobnie tak szybki, jak korzystanie z widoku (innymi słowy, k in large_d.keys()nie jest Pythonem i należy go unikać - jak jest k in large_d.viewkeys()).
Eric O Lebigot

Dziękuję za dostarczenie solidnego, użytecznego przykładu. k in large_djest w rzeczywistości znacznie szybszy niż k in large_d.viewkeys(), więc prawdopodobnie należy tego unikać, ale ma to sens w przypadku k in large_d.viewvalues().
naught101
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.