Struktury danych w Pythonie: dict (słownik)

przez | 24 marca 2020

Cześć! Przygotowałem dla Ciebie już czwarty wpis z serii „Struktury danych w Pythonie”. Tym razem zajmiemy się słownikiem, czyli dictionary, a w skrócie po prostu dict.

Jest to jedna z najważniejszych i najczęściej używanych struktur w Pythonie. W poprzednich wpisach mówiliśmy o listach, krotkach i zbiorach. Mają one to do siebie, że przechowują pojedyncze wartości, np. liczbę, ciąg znaków albo obiekt.

Słownik natomiast jest strukturą przechowującą pary klucz-wartość. W innych językach takie struktury noszą czasem nazwę map albo tablic asocjacyjnych. Jak nazwa wskazuje, przechowują one asocjację, czyli związek między dwoma bytami – kluczem i wartością.

Ok, wystarczy tego wstępu, przejdźmy do rzeczy 😉

Charakterystyka słowników w Pythonie

Słownik (dict) w Pythonie posiada następujące cechy:

  • Jest zmienny (modyfikowalny, mutowalny), czyli możemy dowolnie dodawać, usuwać oraz modyfikować jego elementy.
  • Nie gwarantuje zachowania kolejności elementów (jeśli bardzo zależy nam na kolejności elementów to możemy skorzystać z collections.OrderedDict).
  • Indeksem słownika jest jego klucz
    • Klucz musi być niezmienny, czyli może nim być np. liczba lub string.
    • Krotka jest niezmienna i również może być kluczem, ale tylko jeśli zawiera w sobie wyłącznie stringi, liczby i inne krotki. Jeśli zawiera w sobie jakieś zmienne obiekty to nie może być kluczem.
    • Będąc bardziej dokładnym, klucz musi być hashowalny, czyli spełniać dokładnie takie same kryteria, jak elementy zbioru, czyli set-u. Kwestię tego, czym jest hashowalność, szczegółowo omówiłem w artykule o zbiorach.
  • Klucz musi być unikalny – w jednym słowniku nie może być więcej niż jeden taki sam klucz.
  • Wartością w słowniku może być dowolny obiekt.
    • Wartości różnych kluczy mogą mieć różne typy, np. dla jednego klucza wartość to będzie string, a dla innego lista.
Dowiedz się, do jakich celów możesz wykorzystać Pythona w 2020 roku i tym samym wznieść swoją karierę na wyższy poziom! 🚀

Inicjalizacja

Aby utworzyć nowy, pusty słownik możemy wykorzystać literał, jakim są nawiasy klamrowe {}, albo skorzystać z konstruktora dict():

>>> empty = {}
>>> empty
{}
>>> type(empty)
<class 'dict'>
>>> empty = dict()
>>> empty
{}
>>> type(empty)
<class 'dict'>

Jeśli chcemy uzyskać słownik zawierający na starcie konkretne dane, to wewnątrz nawiasów kwadratowych musimy umieścić pary klucz: wartość (rozdzielone średnikiem i, w zgodzie z Pythonową konwencją, także spacją), oddzielone od siebie przecinkami.

W ostatnim czasie jestem zajarany serialem o Formule 1 na Netflixie, dlatego bohaterami dzisiejszego artykułu o słownikach będą kierowcy F1 oraz ich numery startowe 😉 Jeśli interesuje Cię, skąd wzięły się te numery, to znalazłem ciekawy artykuł, który to wyjaśnia.

drivers = {
    "Lewis Hamilton": 44,
    "Sebastian Vettel": 5,
    "Carlos Sainz": 55,
    "Daniel Ricciardo": 3,
}

W powyższym przykładzie stworzyłem słownik, gdzie kluczem są imię i nazwisko kierowcy, a wartością jego numer startowy.

Tak jak przy pustym słowniku, tutaj też mogę użyć konstruktora dict(). Podaję wtedy do niego iterable, czyli na przykład listę, zawierającą nasze pary klucz-wartość opakowane w krotki:

drivers = dict([("Lewis Hamilton", 44), ("Sebastian Vettel", 5), 
("Carlos Sainz", 55), ("Daniel Ricciardo", 3)])

Jest jeszcze trzeci, całkiem „sprytny” sposób. Możemy użyć dict(), ale dane podać do środka jako keyword arguments, czyli „argumenty nazwane”. Zadziała to jednak tylko wtedy, gdy naszymi kluczami są proste jednowyrazowe stringi. Dlatego musielibyśmy nasz przykład uprościć i użyć samych nazwisk zawodników:

drivers = dict(Hamilton=44, Vettel=5, Sainz=55, Ricciardo=3)

W zależności od sytuacji możesz skorzystać z tego sposobu, który jest akurat dla Ciebie wygodniejszy. Z mojego doświadczenia mogę Ci powiedzieć, że pierwszy sposób z użyciem nawiasów klamrowych jest zdecydowanie najczęściej stosowany.

Operacje na słownikach

Podstawowe operacje

Aby sprawdzić, ile elementów zawiera nasz dict, standardowo użyjemy funkcji len(), tak samo jak w przypadku poprzednio omawianych kolekcji.

>>> len(drivers)
4

Do otrzymania listy wszystkich kluczy w naszym słowniku możemy podać słownik jako argument do konstruktora list():

>>> list(drivers)
['Lewis Hamilton', 'Sebastian Vettel', 'Carlos Sainz', 'Daniel Ricciardo']

Pobieranie wartości

Słowniki są indeksowane po kluczach. Oznacza to, że aby wyciągnąć wartość odpowiadającą danemu kluczowi, użyjemy składni podobnej do list czy krotek, ale zamiast indeksu liczbowego podamy nasz klucz:

>>> drivers["Carlos Sainz"]
55

Jeśli klucz, którego użyjemy, nie istnieje w słowniku, otrzymamy KeyError:

>>> drivers["Robert Kubica"]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
KeyError: 'Robert Kubica'

Możesz uniknąć tego sprawdzając najpierw, czy dany klucz istnieje w słowniku:

>>> "Robert Kubica" in drivers
False

Jednak jest też inny sposób na uniknięcie błędu. Możesz skorzystać z metody get() wywoływanej na obiekcie słownika. Jeśli klucz istnieje, to otrzymasz jego wartość, a jeśli nie, to otrzymasz po prostu None:

>>> drivers.get("Robert Kubica")  # None
>>> drivers.get("Lewis Hamilton")
44

Ciekawym trikiem jest to, że jako drugi argument możemy tutaj podać wartość domyślną (default), która zostanie zwrócona w przypadku jeśli klucz nie zostanie odnaleziony w słowniku:

>>> drivers.get("Robert Kubica", 0)
0
>>> drivers.get("Lewis Hamilton", 0)
44

Dodawanie i edycja elementów

Umówmy się, że oglądanie ciągle tych samych kierowców może być nużące. Dlatego dobrze by było poszerzyć naszą wesołą gromadkę o nowych zawodników 🏎

Dodanie nowej pary klucz-wartość do słownika wygląda podobnie do pobierania wartości dla klucza. W nawiasie kwadratowym podajemy klucz, a po znaku „=” przypisujemy mu odpowiednią wartość:

>>> drivers["Lando Norris"] = 4
>>> drivers
{'Lewis Hamilton': 44, 'Sebastian Vettel': 5, 'Carlos Sainz': 55, 
'Daniel Ricciardo': 3, 'Lando Norris': 4}

Ale uwaga! Jeśli wpis o danym kluczu już istnieje w słowniku, to jego wartość zostanie nadpisana! Zauważ jak poniżej nadpisuję numer startowy Vettela:

>>> drivers["Sebastian Vettel"] = 5555
>>> drivers
{'Lewis Hamilton': 44, 'Sebastian Vettel': 5555, 'Carlos Sainz': 55, 
'Daniel Ricciardo': 3}

Tym samym wiesz już jak edytować wartość w słowniku dla danego klucza – dokładnie tak samo, jak gdybyś dodawał nową parę do słownika.

Usuwanie elementów

Jeśli któryś z kierowców powoduje za dużo problemów na torze, to należałoby go dyscyplinarnie usunąć z wyścigu. Na szczęście w przypadku słownika wystarczy skorzystać z polecenia del:

>>> del drivers["Daniel Ricciardo"]
>>> drivers
{'Lewis Hamilton': 44, 'Sebastian Vettel': 5, 'Carlos Sainz': 55, 
'Lando Norris': 4}

Co jeśli klucz nie istnieje? Musimy przygotować się na wyjątek:

>>> del drivers["Robert Kubica"]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
KeyError: 'Robert Kubica'

Jak sobie z tym poradzić? Możemy najpierw sprawdzić, czy klucz istnieje w słowniku za pomocą in – mówiliśmy o tym wyżej. Innym sposobem będzie opakowanie naszego wywołania del w blok try / except i złapanie tego wyjątku:

try:
    del drivers["Robert Kubica"]
except KeyError:
    print("Driver not found.")

Możemy również do usunięcia elementu ze słownika skorzystać z metody pop(). Tutaj też w przypadku braku szukanego klucza otrzymamy KeyError, choć… niekoniecznie 😉 Bo możemy zdefiniować wartość domyślną, podobnie jak to było wyżej w przypadku get(), i to właśnie ona zostanie zwrócona przy braku szukanego klucza. Taką wartością domyślną może też być na przykład None:

>>> drivers.pop("Lewis Hamilton", None)
44
>>> drivers.pop("Robert Kubica", None). # Otrzymaliśmy None
>>> drivers
{'Sebastian Vettel': 5, 'Carlos Sainz': 55, 'Lando Norris': 4}

Jak widzisz powyżej, używając pop(), podobnie jak to było przy listach i zbiorach, nie tylko usuwamy element ze struktury danych, ale również otrzymujemy go z powrotem i możemy z niego skorzystać. W przypadku słownika nie otrzymujemy z powrotem całej pary klucz-wartość, a jedynie samą wartość, jak widać powyżej – pop-ując Lewisa Hamiltona ze słownika dostaliśmy w odpowiedzi 44, czyli jego wartość.

Iterowanie po słowniku

Najprostszym sposobem iteracji po wszystkich elementach słownika jest wykorzystanie pętli for i podanie do niej po prostu obiektu naszego słownika:

drivers = {
    "Lewis Hamilton": 44,
    "Sebastian Vettel": 5,
    "Carlos Sainz": 55,
    "Daniel Ricciardo": 3,
}

for driver in drivers:
    print(driver)

### Wynik:

Lewis Hamilton
Sebastian Vettel
Carlos Sainz
Daniel Ricciardo

Python jest sprytny i wykrywa, że podaliśmy mu słownik i wie, że ma iterować po kluczach. Dlatego wewnątrz pętli każdy obiekt driver to klucz naszego słownika.

Jeśli chcemy operować na wartościach, musimy wewnątrz pętli użyć zwykłej operacji pobrania wartości, którą poznaliśmy wyżej:

for driver in drivers:
    print(driver, ":", drivers[driver])

### Wynik:

Lewis Hamilton : 44
Sebastian Vettel : 5
Carlos Sainz : 55
Daniel Ricciardo : 3

Innym sposobem jest wykorzystanie metody items(), która zwraca tzw. „widok” elementów słownika. Jest to dynamiczny obiekt – jeśli coś się zmieni w słowniku, coś zostanie dodane, zmienione lub usunięte – to widok będzie odzwierciedlał te zmiany.

Oto co zobaczymy jeśli przeiterujemy po drivers.items():

for driver in drivers.items():
    print(driver)

### Wynik:

('Lewis Hamilton', 44)
('Sebastian Vettel', 5)
('Carlos Sainz', 55)
('Daniel Ricciardo', 3)

Kojarzysz, czym jest pojedyńczy element w pętli? Właśnie dlatego słowniki omawiamy jako ostatnie spośród podstawowych struktur danych w Pythonie, bo do ich pełnego zrozumienia warto znać te poprzednie. Dlatego, że mamy tu do czynienia po prostu z krotkami 😉

for driver in drivers.items():
    print(driver)
    print(type(driver))

### Wynik:

('Lewis Hamilton', 44)
<class 'tuple'>
('Sebastian Vettel', 5)
<class 'tuple'>
('Carlos Sainz', 55)
<class 'tuple'>
('Daniel Ricciardo', 3)
<class 'tuple'>

Wiedząc o tym, że są to krotki, możemy zastosować tuple unpacking, który też już znasz jeśli czytałeś wpis o krotkach, i oto co otrzymamy:

for driver, number in drivers.items():
    print(driver, ":", number)

### Wynik:

Lewis Hamilton : 44
Sebastian Vettel : 5
Carlos Sainz : 55
Daniel Ricciardo : 3

Całkiem zgrabnie to wygląda, prawda? 😉

Podsumowanie

Tutaj się na dzisiaj zatrzymamy, choć o słownikach można by mówić jeszcze dłuuugo! Ale jeśli opanujesz operacje na słownikach, które przedstawiłem w tym artykule, to wystarczy Ci to do 90% przypadków użycia słowników.

Jeśli jednak chcesz poznać dodatkowe triki przydatne w tych pozostałych 10%, to daj znać 🙂 Napisz jakiś komentarz pod tym artykułem albo udostępnij go w social mediach i oznacz mnie w tym poście (Facebook, Instagram, Twitter) – będzie to dla mnie sygnał, że warto cisnąć dalej tego typu materiały 💪

Jeśli jeszcze Cię tam nie ma, to koniecznie dołącz do mojego newslettera! Co tydzień dostaniesz ode mnie zestaw ciekawych linków z branży i będziesz jako pierwszy dowiadywał się o wszystkich moich nowych inicjatywach z zakresu edukacji w IT 😀

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *