Słowniki

https://docs.python.org/3/library/stdtypes.html#mapping-types-dict

WPROWADZENIE

Słownik (dictionary) to nieuporządkowany zbiór par (klucz: wartość), przy czym klucze muszą być różne. Słowniki nie są sekwencjami, ale odwzorowaniami (mapping). Wartości (obiekty) są przechowywane po kluczu, a nie po ich pozycji względnej.

Klucz musi być niezmienny (immutable), zwykle są to liczby lub stringi. Kluczami mogą być krotki (tuple), jeśli zawierają tylko liczby, napisy i krotki.

Próba pobrania nieistniejącego klucza jest błędem (KeyError).

Słowniki są zmienne, mogą być modyfikowane w miejscu, mogą rosnąć lub kurczyć się na życzenie. Słowniki obsługują zagnieżdżanie obiektów na dowolną głębokość.

Od Pythona 3.7 mamy gwarancję, że klucze w słowniku będą uporządkowane w kolejności ich wstawiania do słownika.

+----------------------+---------------------------+
| Operacja             | Znaczenie                 |
+----------------------+---------------------------+
| D = {} ; D = dict()  | pusty słownik             |
| D = {1: "a", 5: "e"} | słownik (dwa klucze)      |
| len(D)               | liczność (liczba par)     |
| D[key] = value       | dodanie pozycji           |
| D[key]               | dostęp do wartości        |
| D = dict(iterable)   | tworzenie z listy par     |
| D2 = dict(D1)        | kopiowanie słownika       |
| D2 = D1.copy()       | stary sposób kopiowania   |
| D2 = {**D1}          | kopiowanie (Py3)          |
| key in D             | zawieranie klucza (bool)  |
| key not in D         |                           |
| D.has_key(key)       | dawny sposób (Py2)        |
| for key in D: pass   | iteracja po kluczach      |
| del D[key]           | usuwanie klucza           |
| D.clear()            | czyszczenie słownika      |
| del D                | usuwanie słownika         |
+----------------------+---------------------------+

D = {}                    # pusty słownik - inicjalizacja
D['one'] = 'jeden'        # dodawanie kluczy
D['two'] = 'dwa'
D['three'] = 'trzy'

# Metody słownika bez parametrów.
D = {k1:v1, k2:v2, k3:v3}
# D = {k1:v1, k2:v2, k3:v3,}   # przecinek na końcu jest dozwolony

D.keys()            # [k1, k2, k3] Py2
D.values()          # [v1, v2, v3] Py2
D.items()           # [(k1, v1), (k2, v2), (k3, v3)] Py2

D.keys()            # zwraca widok kluczy (Py3)
D.values()          # zwraca widok wartości (Py3)
D.items()           # zwraca widok par (Py3)

# Metody słownika z parametrami.
D.has_key('one')    # obecnie zalecane: 'one' in D

# Kopiowanie słownika (shallow copy).
D_alias = D                   # tylko kopiowanie adresu
D_copy1 = D.copy()            # metoda słowników do kopiowania
D_copy2 = dict(D)             # najprostszy sposób
id(D), id(D_alias), id(D_cp), id(D_cp2)

# Usuwanie elementu ze słownika.
del D['two']

# Konwersja listy krotek do dict.
D = dict([("a", 1), ("b", 2), ("c", 3), ("d", 4)])

# Ciekawa pętla po parach - items().
for (char, number) in D.items():
    print("key: {} value: {}".format(char, number))

Sortowanie kluczy dla słowników.


chars = {}                    # inicjalizacja pustego słownika
# chars.get(char) zwraca chars[char] lub None.
# chars.get(char, 0) zwraca chars[char] lub 0.
for char in "abrakadabra":
    chars[char] = chars.get(char, 0) + 1
print(chars)

# Sortowanie par (key, value).
items = chars.items()        # dostaję listę krotek (Py2)
items.sort()

# Sortowanie samych kluczy.
keys = chars.keys()
keys.sort()
[chars[key] for key in keys]     # lista wartości

# Python 2.5 zawiera wbudowaną funkcję sorted(), która dla obiektu
# iterowalnego zwraca posortowaną listę.
for key in sorted(chars):
    print("{} {}".format(key, chars[key]))

Wybrane metody dla słowników.


# Przykładowe inicjalizacje słownika.
D = {k1:v1, k2:v2}                 # klasyczna
D = dict([(k1, v1), (k2, v2)])     # z listy krotek
D = dict(zip([k1, k2], [v1, v2]))  # z dwóch list
D = dict(name1=v1, name2=v2)       # {'name1':v1, 'name2':v2}

if D != {}:         # porównywanie z pustym słownikiem
    print("słownik nie jest pusty")
if D:               # pusty słownik oznacza False
    print("słownik nie jest pusty")   # jw

D.clear()                     # wyczyszczenie słownika
E = D.copy()                  # kopia słownika
E = dict(D)                   # jw, ale wygodniej
D.has_key(key)                # zwraca True/False
key in D                      # jw, zalecane

D.items()                     # zwraca listę krotek (key, value) (Py2)
D.keys()                      # zwraca listę kluczy (Py2)
D.values()                    # zwraca listę wartości (Py2)

D.get(key)                    # zwraca D[key] lub None
D.get(key, value)             # zwraca D[key] lub value
D.setdefault(key, value)
# if key in D then return D[key],
# if key not in D then set D[key] = value and return value

D.pop(key)                    # zwraca D[key] i usuwa ze słownika
D.pop(key, value)             # zwraca D[key] lub value, gdy key nie ma w słowniku
D.popitem()                   # zwraca 2-tuple (key, value) [chyba losowo] 
                              # lub Error, gdy słownik jest pusty
D = dict.fromkeys(S[, value])    # S to sekwencja, value to domyślnie None
D = dict.fromkeys(range(4), 0)   # {0: 0, 1: 0, 2: 0, 3: 0}
D = dict.fromkeys("abc", 1)   # {'a': 1, 'c': 1, 'b': 1}

D.update(E)   # E to dict, czyli for k in E: D[k] = E[k]
D.update(**E)   # E to dict, jw
D.update(F)   # F=[("x",10),("y",20)], czyli for (k,v) in F: D[k] = v
D.update(name1=value1, name2=value2)
# inaczej D.update({'name1': value1, 'name2': value2})

Problem sklejania dwóch słowników.


# http://stackoverflow.com/questions/38987/how-can-i-merge-two-python-dictionaries-in-a-single-expression?rq=1
# Dane są slowniki A i B. Utworzyć C jako sumę słowników.
C = A.copy()
C.update(B)

# Można utworzyć specjalną funkcję (zalecane rozwiązanie).
def merge_dict(a, b):
    c = a.copy()
    c.update(b)
    return c
# Użycie: C = merge_dict(A, B)

# Uogólnienie działa w Py2 i Py3.
def merge_dicts(*dict_args):
    '''
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
    '''
    result = dict()
    for d in dict_args:
        result.update(d)
    return result

SŁOWNIKI SYSTEMOWE

locals() zwraca słownik zawierający current scope's local variables, czyli lokalnie dostępne nazwy.

vars() jest równoważne locals().

vars([object]) jest równoważne object.__dict__, czyli dostajemy słownik będący przestrzenią nazw obiektu, zawierającą np. atrybuty obiektu.

DZIEDZICZENIE ZE SŁOWNIKÓW

Od Pythona 2.5 w klasie dziedziczonej z dict można zdefiniować metodę __missing__(). Wtedy przy wywołaniu D[key], jeżeli klucz nie istnieje, to wywoływana jest metoda __missing__(key). Można określić pożądane zachowanie, np. inne niż stała domyślna wartość. Daje to większą elastyczność niż metody get() czy setdefault().

PYTHON 3

Metody słowników zwracają iteratory, a nie gotowe listy. Jeżeli chcemy zobaczyć gotową listę trzeba użyć konwersji.


list(D.items())               # zwraca listę krotek (key, value)
list(D.keys())                # zwraca listę kluczy
list(D.values())              # zwraca listę wartości

Obiekty zwracane przez metody słowników są dynamiczne, to są widoki (views). Można po nich iterować, można sprawdzać należenie do obiektu (membership test).


>>> D = { "first" : 1, "second" : 2 }
>>> ks = D.keys()
>>> ks
dict_keys(['second', 'first'])
>>> D["third"] = 3
>>> ks
dict_keys(['second', 'third', 'first'])   # widać zmianę
>>> 'second' in ks   # membership test
True

Nie ma metody D.has_key(key), stosuje się konstrukcję 'key in D'.

Występuje konstrukcja słowników składanych (dict comprehension). Wydaje się, że można to łatwo zastąpić przez konstrukcję z listą par (key, value),


keys = [k1, k2, k3]
values = [v1, v2, v3]
D = dict(zip(keys, values))                  # Py2 i Py3
D = {k: v for (k, v) in zip(keys, values)}   # Py3 i Py2.7

D = {x: x**2 for x in range(9)}        # Py3 i Py2.7
D = dict((x, x**2) for x in range(9))    # generator

D = {x: x*3 for x in "qwerty"}          # Py3 i Py2.7
D = dict((x, x*3) for x in "qwerty")    # generator

# Korzystanie z operatora ** (Py3).
# Ta sama idea dla argumentów funkcji (**keywords).
D1 = {"a": 1, "b": 2}
D2 = {"c": 3, "d": 4}
merged_dict = {**D1, **D2}   # unpacking in a single line
print(merged_dict)   # {"a": 1, "b": 2, "c": 3, "d": 4}
# Problem: merged_dict to zawsze podstawowy słownik, a D1 i D2
# mogą być podklasami słowników, np. defaultdict!

# Dawny sposób.
merged_dict = {}
merged_dict.update(D1)
merged_dict.update(D2)

# PEP 584 -- Add Union Operators To dict
# Dodanie operatorów '|' i '|=', tak jak dla list.

D3 = D1 | D2   # unia słowników tworzy nowy słownik (Py3.9.7)
# UWAGA Jeżeli ten sam klucz jest w D1 i D2, to wygrywa D2.
# Ogólnie D1 | D2 to nie to samo, co D2 | D1.

D1 |= D2   # działa jak D1.update(D2), in-place (augmented assignment)
# Przypomnienie: dla list mamy L1 += L2, co działa jak L1.extend(L2), in-place