Словари (dict) в Python — полный гайд для аналитика

Коротко

Словарь (dict) — основная структура данных для хранения пар «ключ — значение» в Python. Аналитики используют словари постоянно: маппинг кодов в названия, подсчёт вхождений, хранение конфигов и параметров запросов. На собеседованиях спрашивают про разницу [] и .get(), мутабельность ключей и сложность операций.

Создание словаря

# Литерал — самый частый способ
user = {'name': 'Анна', 'age': 25, 'city': 'Москва'}

# Конструктор dict()
user = dict(name='Анна', age=25, city='Москва')

# Из списка кортежей
pairs = [('name', 'Анна'), ('age', 25)]
user = dict(pairs)

# dict.fromkeys() — все значения одинаковые
metrics = dict.fromkeys(['dau', 'wau', 'mau'], 0)
# {'dau': 0, 'wau': 0, 'mau': 0}

# Пустой словарь
empty = {}

Ключи должны быть хешируемыми: строки, числа, tuple. Списки и другие dict ключами быть не могут.

Доступ к элементам: [] vs .get()

config = {'host': 'localhost', 'port': 5432, 'db': 'analytics'}

# Квадратные скобки — бросает KeyError, если ключа нет
config['host']       # 'localhost'
config['password']   # KeyError: 'password'

# .get() — возвращает None (или значение по умолчанию)
config.get('host')        # 'localhost'
config.get('password')    # None
config.get('password', 'secret')  # 'secret'

Правило: используйте [], когда ключ точно есть (или хотите явную ошибку). .get() — когда ключа может не быть.

Добавление и обновление

user = {'name': 'Анна', 'age': 25}

# Добавить / перезаписать по ключу
user['city'] = 'Москва'
user['age'] = 26

# update() — добавить/обновить сразу несколько
user.update({'age': 27, 'role': 'analyst'})

# В Python 3.9+ можно через оператор |
merged = user | {'company': 'Яндекс', 'level': 'middle'}

Удаление

data = {'a': 1, 'b': 2, 'c': 3}

# del — удаляет по ключу, KeyError если нет
del data['a']

# pop() — удаляет и возвращает значение
val = data.pop('b')       # val = 2
val = data.pop('z', None) # None, без KeyError

# clear() — очистить весь словарь
data.clear()               # {}

Методы keys, values, items

params = {'metric': 'revenue', 'period': 'month', 'segment': 'new'}

list(params.keys())    # ['metric', 'period', 'segment']
list(params.values())  # ['revenue', 'month', 'new']
list(params.items())   # [('metric', 'revenue'), ('period', 'month'), ('segment', 'new')]

# Проверка наличия ключа
'metric' in params     # True
'channel' in params    # False

keys(), values(), items() возвращают view-объекты — они обновляются при изменении словаря.

Итерирование

scores = {'sql': 85, 'python': 92, 'stats': 78}

# По ключам (по умолчанию)
for key in scores:
    print(key)

# По парам ключ-значение
for key, value in scores.items():
    print(f'{key}: {value}')

# По значениям
for value in scores.values():
    print(value)

Порядок итерации совпадает с порядком вставки (гарантировано с Python 3.7+).

Dict comprehension

# Базовый синтаксис
{ключ: значение for элемент in итерируемый_объект}

# Квадраты чисел
{x: x ** 2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# С фильтрацией
prices = {'яблоко': 80, 'манго': 350, 'банан': 60, 'авокадо': 290}
cheap = {k: v for k, v in prices.items() if v < 200}
# {'яблоко': 80, 'банан': 60}

# Инвертировать словарь
original = {'MSK': 'Москва', 'SPB': 'Петербург'}
inverted = {v: k for k, v in original.items()}
# {'Москва': 'MSK', 'Петербург': 'SPB'}

Подробнее про синтаксис comprehensions — в статье про list comprehension.

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

users = {
    101: {'name': 'Анна', 'scores': {'sql': 85, 'python': 92}},
    102: {'name': 'Борис', 'scores': {'sql': 78, 'python': 88}},
}

# Доступ
users[101]['name']              # 'Анна'
users[101]['scores']['sql']     # 85

# Безопасный доступ к вложенным ключам
users.get(999, {}).get('name')  # None вместо KeyError

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

from collections import defaultdict

# Подсчёт вхождений
word_count = defaultdict(int)
for word in ['sql', 'python', 'sql', 'sql', 'python']:
    word_count[word] += 1
# defaultdict(int, {'sql': 3, 'python': 2})

# Группировка
by_city = defaultdict(list)
for name, city in [('Анна', 'Москва'), ('Борис', 'Москва'), ('Вика', 'СПб')]:
    by_city[city].append(name)
# {'Москва': ['Анна', 'Борис'], 'СПб': ['Вика']}

defaultdict автоматически создаёт значение при обращении к несуществующему ключу — не нужны проверки и setdefault().

Примеры для аналитика

# Маппинг кодов в названия
status_map = {0: 'новый', 1: 'активный', 2: 'отток'}
user_status = status_map.get(row['status_code'], 'неизвестно')

# Подсчёт событий
from collections import Counter
events = ['page_view', 'click', 'page_view', 'purchase', 'click', 'click']
Counter(events)
# Counter({'click': 3, 'page_view': 2, 'purchase': 1})

# Параметры конфигурации
query_params = {
    'date_from': '2026-01-01',
    'date_to': '2026-03-31',
    'metrics': ['revenue', 'orders'],
    'segment': 'organic',
}

Частые ошибки

KeyError вместо .get(). Забыли, что ключа может не быть — получили необработанное исключение. Привычка использовать .get() с дефолтом спасает.

Мутабельный объект как значение по умолчанию. Классический баг:

# Опасно — один и тот же список для всех вызовов
def add_tag(tag, tags={}):
    tags[tag] = True
    return tags

add_tag('sql')     # {'sql': True}
add_tag('python')  # {'sql': True, 'python': True} — сюрприз!

# Правильно
def add_tag(tag, tags=None):
    if tags is None:
        tags = {}
    tags[tag] = True
    return tags

Изменение словаря во время итерации. RuntimeError: dictionary changed size during iteration — нельзя добавлять/удалять ключи в цикле. Решение: итерировать по копии list(d.keys()).

Вопросы с собеседований

Чем dict[key] отличается от dict.get(key)?[] бросает KeyError, если ключа нет. .get() возвращает None или указанное значение по умолчанию. Используйте [], когда ключ обязан существовать, .get() — когда может отсутствовать.

Какие объекты могут быть ключами словаря? — Только хешируемые (immutable): str, int, float, tuple (если все элементы хешируемы), frozenset. list и dict — нельзя, потому что они мутабельные и не имеют стабильного хеша.

Какова сложность поиска по ключу в dict? — В среднем O(1) благодаря хеш-таблице. В худшем случае O(n) из-за коллизий, но на практике это крайне редко.

Сохраняется ли порядок элементов в dict? — Да, с Python 3.7+ порядок вставки гарантирован спецификацией языка. В CPython 3.6 это было деталью реализации, но не гарантией.

Чем dict отличается от defaultdict? — defaultdict автоматически создаёт значение при обращении к несуществующему ключу (через фабрику: int, list, set). Обычный dict бросает KeyError.

Потренировать Python-вопросы на практике можно в тренажёре Карьерника. Подробнее про list comprehension и разницу list vs tuple. Больше примеров вопросов — на отдельной странице.

Открыть тренажёр в Telegram — вопросы по Python, pandas, SQL и аналитике. Бесплатно.

FAQ

Когда использовать dict, а когда list?

dict — когда нужен быстрый доступ по ключу: маппинги, конфиги, кеши, подсчёт вхождений. list — когда важен порядок и индексация, а ключи не нужны. Если данные — пары «ключ — значение», это dict.

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

JSON — текстовый формат сериализации. dict — структура данных в памяти Python. json.loads() превращает JSON-строку в dict, json.dumps() — обратно. Ключи JSON — только строки, в dict ключом может быть любой хешируемый объект.

Можно ли сортировать словарь?

Сам dict не имеет метода sort. Но можно создать новый отсортированный словарь: dict(sorted(d.items(), key=lambda x: x[1])). Это создаёт новый dict с элементами в нужном порядке.

Что лучше: несколько if-проверок или dict-маппинг?

Dict-маппинг чище и быстрее. Вместо цепочки if status == 0: ... elif status == 1: ... лучше status_map = {0: 'новый', 1: 'активный'} и status_map.get(status, 'неизвестно'). Особенно при большом количестве вариантов.