Список и словарь — два первых «контейнера», с которыми ребёнок сталкивается на курсе Python. До этого переменная держала одно значение: одно имя, одно число. Потом появляется задача, где значений двадцать, и возникает вопрос без очевидного ответа: куда их положить. Именно здесь большинство и путается — не в синтаксисе, а в выборе структуры.

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

Список: значения по порядку

Список — это пронумерованная последовательность. Записывается квадратными скобками, значения через запятую. Нумерация начинается с нуля, и это первое, что сбивает с толку: в списке из трёх элементов последний имеет номер два, а не три.

ocenki = [9, 7, 10]
print(ocenki[0])    # 9
print(ocenki[2])    # 10
print(len(ocenki))  # 3

Список можно менять после создания — добавлять, удалять, переворачивать. Вот операции, которых хватает на девять задач из десяти.

Что делаемКакЧто произойдёт
Добавить в конецspisok.append(5)список стал длиннее на один элемент
Вставить на позициюspisok.insert(0, 5)пятёрка станет первой, остальное сдвинется
Удалить по значениюspisok.remove(5)исчезнет первая пятёрка, другие останутся
Удалить по номеруspisok.pop(0)элемент удалён и возвращён как результат
Сколько элементовlen(spisok)число
Отсортироватьspisok.sort()меняет сам список, ничего не возвращает
Перевернуть порядокspisok.reverse()порядок стал обратным
Найти номерspisok.index(5)номер первой пятёрки
Посчитать вхожденияspisok.count(5)сколько раз встретилась пятёрка
Есть ли значение5 in spisokTrue или False

Две строки из этой таблицы стоят отдельного упоминания, потому что именно на них спотыкаются. Метод sort() ничего не возвращает — он перестраивает список на месте. Запись ocenki = ocenki.sort() выглядит логично и убивает данные: в переменной остаётся None, а список перед этим всё-таки отсортировался, только достать его уже негде. Если нужна отсортированная копия, а оригинал должен выжить, берите sorted(ocenki) — это другая функция, и она как раз возвращает новый список.

Второе — remove() убирает только первое совпадение. В списке [5, 5, 5] после remove(5) останется две пятёрки, а не ноль. Ребёнок, который этого не знает, пишет цикл, который «не дочищает» список, и потом ищет ошибку в цикле, хотя цикл правильный.

Словарь: значение по имени

Словарь хранит пары «ключ — значение». Вместо номера вы сами называете, под каким именем лежит значение. Записывается фигурными скобками, пара разделяется двоеточием.

igrok = {'imia': 'Olia', 'ochki': 120, 'uroven': 3}
print(igrok['ochki'])        # 120
igrok['ochki'] = 150         # заменили
igrok['zhizni'] = 3          # добавили новый ключ

Порядка «по номеру» здесь нет: обращаются всегда по ключу. Новые ключи добавляются простым присваиванием — отдельной команды на это не нужно, и именно поэтому словарь так удобен, когда набор полей заранее неизвестен.

Что делаемКакЧто произойдёт
Прочитать значениеd['imia']значение или ошибка KeyError
Прочитать безопасноd.get('imia')значение или None, без падения
Значение по умолчаниюd.get('ochki', 0)ноль, если ключа нет
Добавить или заменитьd['uroven'] = 4одна команда на оба случая
Удалить ключd.pop('uroven')ключ исчез, значение вернулось
Есть ли ключ'imia' in dTrue или False
Все ключиd.keys()перечень имён
Все значенияd.values()перечень значений
Пары вместеd.items()для обхода циклом
Слить два словаряd.update(drugoi)общие ключи перезапишутся

Самая полезная строка здесь — get(). Обращение d['ochki'] к отсутствующему ключу останавливает программу с KeyError, и для ребёнка это выглядит поломкой на ровном месте: вчера работало, сегодня нет, а на самом деле просто изменились данные. Тот же d.get('ochki', 0) в этой ситуации отдаёт ноль, и программа идёт дальше. На занятиях мы показываем get() сразу после квадратных скобок, чтобы привычка появилась раньше первой аварии.

Обход словаря циклом почти всегда выглядит так — через items(), потому что нужны обе половины пары:

for kliuch, znachenie in igrok.items():
    print(kliuch, '=', znachenie)

Если цикл написать без items(), то есть for k in igrok, он пройдёт только по ключам. Это не ошибка, а другая задача: так делают, когда значения не нужны, например чтобы проверить набор полей.

Срезы: берём часть списка

Отдельно от таблицы стоит запись с двоеточием внутри квадратных скобок — срез. Он возвращает не один элемент, а новый список из нескольких, и это самый быстрый способ взять «первые три» или «всё, кроме последнего», не написав ни одного цикла.

chisla = [10, 20, 30, 40, 50]
print(chisla[1:3])   # [20, 30]
print(chisla[:2])    # [10, 20]
print(chisla[-2:])   # [40, 50]
print(chisla[::2])   # [10, 30, 50]

Главное про срез: правая граница не входит. Запись [1:3] даёт элементы с номерами один и два, а третий остаётся за бортом. Логика в том, что разница границ сразу даёт длину результата: три минус один — два элемента. Привыкается быстро, но первые несколько раз ответ выглядит обрезанным на один.

Второе полезное свойство: срез всегда создаёт копию. Поэтому kopiia = chisla[:] — самый короткий способ получить отдельный список, который можно портить, не задевая оригинал. Простое kopiia = chisla копии не делает: оба имени указывают на один и тот же список, и изменение через одно имя видно через другое. Это источник половины «загадочных» ошибок в первых играх, где список монет почему-то меняется сам.

Когда структуры вкладываются одна в другую

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

igroki = [
    {'imia': 'Olia', 'ochki': 120},
    {'imia': 'Maks', 'ochki': 95},
]
for g in igroki:
    print(g['imia'], g['ochki'])

Читается это справа налево: igroki[0]['imia'] означает «у нулевого игрока взять поле имени». Ребёнок, который один раз собрал такую структуру сам, дальше уже не путает квадратные скобки с фигурными, потому что видит, что каждые отвечают за своё.

Когда список, а когда словарь

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

ЗадачаСтруктураПочему
Оценки за контрольные подрядсписокважен порядок, имена не нужны
Количество жизней у каждого игрокасловарьобращаемся по имени игрока
Очередь ходов в игресписокпервый, второй, третий
Настройки программысловарьу каждого параметра своё имя
Сколько раз встретилось словословарьслово — ключ, количество — значение

Признак, что структура выбрана неудачно: чтобы достать нужное значение, приходится писать цикл с проверкой. Если в списке лежат пары вида ['Olia', 120] и для оценок Оли вы каждый раз перебираете весь список — это словарь, записанный списком. Переложите в словарь, и цикл исчезнет вместе с ошибками в нём.

Где спотыкаются чаще всего

Три вещи, которые мы видим на занятиях из недели в неделю. Первая: нумерация с нуля и попытка обратиться к spisok[len(spisok)]. Последний элемент — это len - 1, или просто spisok[-1], и вторая запись короче и надёжнее, потому что не сломается, когда список станет длиннее.

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

Третья: ключом словаря не может быть список. Числа, строки, кортежи — можно, список — нет, и сообщение unhashable type ребёнку об этом ничего не говорит. Достаточно знать саму границу: ключ должен быть неизменяемым.

Обе структуры встречаются в каждой следующей теме — от обработки файлов до работы с API, поэтому лишнее занятие на них выгоднее, чем возвращаться потом. Как они выглядят в цикле for, мы разбирали отдельно, а типичные ошибки новичков собраны в своём справочнике. Что из этого складывается за первый год — в материале про проекты первого года.

Самый короткий способ проверить, что тема усвоена: попросите ребёнка переписать любую свою старую программу со списка на словарь или наоборот. Если он может сказать, что именно стало удобнее, а что хуже, выбор структуры он уже делает осознанно.