Список и словарь — два первых «контейнера», с которыми ребёнок сталкивается на курсе 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 spisok | True или 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 d | True или 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, мы разбирали отдельно, а типичные ошибки новичков собраны в своём справочнике. Что из этого складывается за первый год — в материале про проекты первого года.
Самый короткий способ проверить, что тема усвоена: попросите ребёнка переписать любую свою старую программу со списка на словарь или наоборот. Если он может сказать, что именно стало удобнее, а что хуже, выбор структуры он уже делает осознанно.