# Словари в Python (dict): создание, методы, перебор, сортировка и frozendict

URL: https://skillaria.top/ru/guides/python/slovari-python

Словарь в Python (dict) — изменяемая коллекция пар «ключ — значение», которая находит значение по ключу практически мгновенно; создаётся фигурными скобками {"кофе": 250} или функцией dict(). Ключи уникальны и должны быть хешируемыми, с Python 3.7 порядок вставки сохраняется, а в 3.15 появился встроенный неизменяемый вариант — frozendict.

Словарь в Python (`dict`) — это коллекция пар «ключ — значение», в которой значение находится по ключу практически мгновенно, сколько бы записей там ни было. Если вам нужно посчитать траты по категориям, хранить настройки или быстро искать пользователя по id, вы берёте словарь. Ниже — всё, что нужно для работы с python dict: создание, методы, перебор, сортировка, объединение и новинка 3.15 — `frozendict`.

## Что такое словарь в Python

Словарь хранит пары, где ключ уникален: запишете значение по существующему ключу — старое заменится. Внутри это хеш-таблица, поэтому поиск, добавление и удаление по ключу работают в среднем за постоянное время, а не перебором.

Ключом может быть только хешируемый объект: строка, число, кортеж из неизменяемых значений. Список ключом быть не может — получите `TypeError: unhashable type: 'list'`. Значения же могут быть любыми, в том числе другими словарями и списками.

С Python 3.7 словарь гарантированно помнит порядок вставки: перебирая его, вы получите ключи в том порядке, в котором их добавляли.

## Как создать словарь

Есть несколько способов создания словаря, и все они встречаются в реальном коде.

```
spent = {"Еда": 1200, "Такси": 450}       # литерал
empty = {}                                 # пустой словарь
also_empty = dict()                        # то же через функцию dict
user = dict(name="Анна", age=30)           # именованные аргументы
pairs = dict([("a", 1), ("b", 2)])         # из списка пар
keys = dict.fromkeys(["x", "y"], 0)        # {'x': 0, 'y': 0}
```

Обратите внимание: пустые фигурные скобки `{}` создают именно пустой словарь, а не множество. Пустое множество делается только через `set()`.

Удобный способ построить словарь из двух списков — `zip`:

```
names = ["Еда", "Такси", "Кино"]
sums = [1200, 450, 600]
spent = dict(zip(names, sums))
print(spent)   # {'Еда': 1200, 'Такси': 450, 'Кино': 600}
```

## Доступ по ключу: d[k] и get

Значение читается квадратными скобками. Если ключа нет, Python бросит `KeyError`. Чтобы ошибки не было, используйте метод `get`: он вернёт `None` или значение по умолчанию.

```
spent = {"Еда": 1200}
print(spent["Еда"])            # 1200
print(spent.get("Кино"))       # None
print(spent.get("Кино", 0))    # 0
spent["Кино"]                  # KeyError: 'Кино'
```

Проверить, есть ли ключ в словаре, можно оператором `in`: `"Еда" in spent` вернёт `True`. Он проверяет именно ключи, не значения. Для значений пишите `1200 in spent.values()`.

## Добавление, изменение и удаление

Добавить или обновить запись — одна строка присваивания. Для подсчётов очень удобна связка с `get`:

```
spent = {}
for category, amount in [("Еда", 250), ("Такси", 300), ("Еда", 400)]:
    spent[category] = spent.get(category, 0) + amount
print(spent)   # {'Еда': 650, 'Такси': 300}
```

Удалить ключ можно через `del d[k]` (бросит `KeyError`, если ключа нет) или `d.pop(k, default)`, который ещё и вернёт значение. Очистить словарь целиком — `d.clear()`. Длина словаря, то есть число пар, — `len(d)`.

## Методы словарей: шпаргалка

| Метод | Что делает |
| --- | --- |

| `get(k, default)` | значение по ключу или default без ошибки |

| `setdefault(k, default)` | вернёт значение; если ключа нет — сначала запишет default |

| `update(other)` | добавит и перезапишет пары из другого словаря |

| `pop(k, default)` | удалит ключ и вернёт значение |

| `popitem()` | удалит и вернёт последнюю добавленную пару |

| `keys()`, `values()`, `items()` | представления ключей, значений и пар |

| `copy()` | поверхностная копия |

| `clear()` | очистить словарь |

| `fromkeys(keys, value)` | новый словарь с одинаковым значением |

### setdefault: заполнить словарь списками

Метод `setdefault` выручает, когда значения — списки, и нужно группировать:

```
groups = {}
for item, cat in [("кофе", "Еда"), ("метро", "Транспорт"), ("пицца", "Еда")]:
    groups.setdefault(cat, []).append(item)
print(groups)   # {'Еда': ['кофе', 'пицца'], 'Транспорт': ['метро']}
```

### update: обновить словарь

`update` меняет словарь на месте: новые ключи добавляются, совпадающие перезаписываются.

```
cfg = {"debug": False, "lang": "ru"}
cfg.update({"debug": True, "theme": "dark"})
print(cfg)   # {'debug': True, 'lang': 'ru', 'theme': 'dark'}
```

## defaultdict: словарь со значением по умолчанию

`collections.defaultdict` — подкласс dict, который при обращении к отсутствующему ключу сам создаёт значение фабрикой. Код подсчётов и группировок становится короче.

```
from collections import defaultdict

spent = defaultdict(int)
for cat, amount in [("Еда", 250), ("Такси", 300), ("Еда", 400)]:
    spent[cat] += amount
print(dict(spent))   # {'Еда': 650, 'Такси': 300}

groups = defaultdict(list)
groups["Еда"].append("кофе")
```

Для простого подсчёта количества есть ещё `collections.Counter`: `Counter(["a", "b", "a"])` даст `Counter({'a': 2, 'b': 1})`.

## Перебор словаря: цикл for

Обход словаря в цикле `for` по умолчанию идёт по ключам. Чтобы получить сразу ключ и значение, перебирайте `items()` — это самый частый вариант.

```
spent = {"Еда": 650, "Такси": 300}

for cat in spent:                    # ключи
    print(cat)

for amount in spent.values():        # значения
    print(amount)

for cat, amount in spent.items():    # пары
    print(f"{cat}: {amount} ₽")
```

Менять размер словаря прямо во время перебора нельзя — будет `RuntimeError: dictionary changed size during iteration`. Если нужно удалить часть ключей, перебирайте копию: `for k in list(d):`.

## Вывод словаря на экран

`print(d)` печатает словарь в одну строку. Для большого или вложенного словаря удобнее модуль `pprint` или `json.dumps(d, ensure_ascii=False, indent=2)` — он выведет данные с отступами и кириллицей без экранирования.

## Сортировка словаря

Сам словарь не сортируется на месте, у него нет метода `sort`. Сортировка словаря — это создание нового словаря из отсортированных пар через `sorted`.

```
spent = {"Такси": 300, "Еда": 650, "Кино": 120}

by_key = dict(sorted(spent.items()))
print(by_key)    # {'Еда': 650, 'Кино': 120, 'Такси': 300}

by_value = dict(sorted(spent.items(), key=lambda p: p[1], reverse=True))
print(by_value)  # {'Еда': 650, 'Такси': 300, 'Кино': 120}
```

Если нужны только топ-N категорий, не превращайте результат обратно в словарь: `sorted(spent.items(), key=lambda p: p[1], reverse=True)[:2]` вернёт список пар.

Отсортировать список словарей по полю можно так: `sorted(users, key=lambda u: u["age"])`.

## Объединение и сложение словарей

С Python 3.9 словари объединяются оператором `|`, а `|=` обновляет словарь на месте. При совпадении ключей побеждает правый операнд.

```
a = {"x": 1, "y": 2}
b = {"y": 20, "z": 3}
print(a | b)          # {'x': 1, 'y': 20, 'z': 3}
print({**a, **b})     # то же через распаковку
a |= b                # как a.update(b)
```

Обратите внимание: `a + b` для словарей не работает, будет `TypeError`. Если же нужно именно сложить значения по одинаковым ключам, используйте `Counter`: `Counter(a) + Counter(b)`.

### Распаковка словаря

Две звёздочки `**` распаковывают словарь в именованные аргументы функции или в другой словарь:

```
def greet(name, city):
    return f"{name} из {city}"

data = {"name": "Анна", "city": "Казани"}
print(greet(**data))   # Анна из Казани
```

## Генератор словаря (dict comprehension)

Генератор словарей строит словарь одной строкой из любой последовательности:

```
squares = {n: n * n for n in range(5)}
print(squares)   # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

big = {k: v for k, v in spent.items() if v > 200}

inverted = {v: k for k, v in {"a": 1, "b": 2}.items()}
print(inverted)  # {1: 'a', 2: 'b'}
```

Последний пример — как перевернуть словарь, поменяв ключи и значения местами. Работает, если значения уникальны и хешируемы, иначе часть пар потеряется.

## Вложенные словари

Значением может быть другой словарь — так описывают структуры вроде JSON-ответов API или словарь словарей пользователей.

```
users = {
    101: {"name": "Анна", "skills": ["python", "sql"]},
    102: {"name": "Олег", "skills": ["go"]},
}
print(users[101]["skills"][0])            # python
print(users.get(103, {}).get("name"))     # None, без KeyError
```

## Копирование словаря: копия и второе имя

Строка `b = a` не копирует словарь, а вешает на тот же объект второе имя. Измените через `b` — изменения увидите и в `a`. То же происходит при передаче словаря в функцию: она получает ссылку на общий объект и может его тихо испортить.

```
import copy

a = {"limits": [0, 500]}
b = a.copy()              # поверхностная копия
b["limits"].append(1000)  # вложенный список общий!
print(a)                  # {'limits': [0, 500, 1000]}

c = copy.deepcopy(a)      # полная независимая копия
```

## Сравнение словарей

Оператор `==` сравнивает словари по содержимому: равны, если совпадают все пары. Порядок ключей при этом не учитывается: `{"a": 1, "b": 2} == {"b": 2, "a": 1}` даёт `True`. Чтобы найти общие ключи, используйте операции множеств над представлениями: `a.keys() & b.keys()`.

## Типизированный словарь: аннотации

Аннотация словаря пишется как `dict[str, int]`. Если у словаря фиксированный набор полей разных типов, удобен `TypedDict` из модуля `typing`:

```
from typing import TypedDict

class User(TypedDict):
    name: str
    age: int

u: User = {"name": "Анна", "age": 30}
```

Аннотации не проверяются во время выполнения, их читают IDE и анализаторы вроде mypy.

## Неизменяемый словарь: frozendict в Python 3.15

В Python 3.15 появился встроенный тип `frozendict` (PEP 814): импорт не нужен, а попытка изменить его сразу заканчивается `TypeError`. Раньше неизменяемый словарь приходилось изображать обёрткой из модуля `types` или сторонним пакетом. Это ровно то, что нужно для общих настроек, которые никакая функция не должна тихо поменять.

```
config = frozendict(min_amount=0, max_amount=500)
config["max_amount"] = 1000
# TypeError: 'frozendict' object does not support item assignment

strict = config | {"max_amount": 300}   # новый frozendict, оригинал цел
cache = {config: "отчёт за май"}        # хешируем — годится ключом
```

Что важно знать, по материалам курса:

- `frozendict` наследует от `object`, а не от `dict`, поэтому `isinstance(cfg, dict)` вернёт `False`. Для проверки «любой словарь» используйте `collections.abc.Mapping` или `isinstance(x, (dict, frozendict))`.
- Он хешируем, только если хешируемы ключи и значения: `frozendict(a=[1])` создаётся, но `hash()` упадёт. Неизменяемость неглубокая, как у кортежа.
- Порядок хранится, но `==` его не учитывает.
- `json.dumps`, `pickle` и `copy` понимают frozendict.

## Типичные ошибки

- **KeyError** при чтении — используйте `get` или проверку `in`.
- **Список в роли ключа** — превратите его в кортеж: `d[tuple(lst)]`.
- **Изменение словаря в цикле** — перебирайте `list(d)`.
- **Ожидание копии от присваивания** — делайте `copy()` или `deepcopy`.
- **`{}` вместо пустого множества** — это пустой словарь.

## Итог

Python dict — главная структура данных языка: быстрый поиск по ключу, сохранённый порядок вставки, удобные методы `get`, `setdefault`, `update`, перебор через `items()`, сортировка через `sorted` и объединение оператором `|`. Помните про копирование и хешируемые ключи, а для настроек в Python 3.15 берите `frozendict`. Словари, списки, кортежи и множества разобраны на практике в курсе «Python 3.15: от новичка до профи» на примере трекера трат.

## FAQ

**Можно ли хранить в словаре значения разных типов?**

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

**Как узнать количество элементов в словаре?**

Функция len(d) возвращает число пар ключ — значение. Для пустого словаря она вернёт 0, а сам пустой словарь в условии if считается ложным.

**Чем dict отличается от defaultdict?**

defaultdict — подкласс dict, который при обращении к отсутствующему ключу не бросает KeyError, а создаёт значение через фабрику, например int или list. Во всём остальном он ведёт себя как обычный словарь.

**Как получить ключ по значению?**

Прямого метода нет, нужен перебор: next((k for k, v in d.items() if v == value), None). Если так ищут часто, проще построить обратный словарь генератором {v: k for k, v in d.items()}.

**Как сохранить словарь в файл?**

Чаще всего через модуль json: json.dump(d, f, ensure_ascii=False, indent=2) в файл, открытый на запись, и json.load(f) для чтения. Ключи при этом станут строками.


---
Source: Skillaria.Top (https://skillaria.top)
