Список і словник — два перші «контейнери», з якими дитина стикається на курсі Python. Доти змінна тримала одне значення: одне ім'я, одне число. Потім з'являється задача, де значень двадцять, і виникає питання без очевидної відповіді: куди їх покласти. Саме тут більшість і плутається — не в синтаксисі, а у виборі структури.

Ця стаття — довідник, до якого можна повертатися між заняттями. Спершу про списки, потім про словники, потім про те, коли брати який. Усі приклади короткі навмисно: їх можна набрати вручну і одразу подивитися, що вийде. Читати з екрана тут марно — поки дитина сама не зіпсує список і не полагодить його, різниця між двома структурами залишається словами.

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

Список — це пронумерована послідовність. Записується квадратними дужками, значення через кому. Нумерація починається з нуля, і це перше, що збиває з пантелику: у списку з трьох елементів останній має номер два, а не три.

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

Список можна змінювати після створення — додавати, видаляти, перевертати. Ось операції, яких вистачає на дев'ять задач з десяти.

Що робимоЯкЩо станеться
Додати в кінецьspysok.append(5)список подовшав на один елемент
Вставити на позиціюspysok.insert(0, 5)п'ятірка стане першою, решта зсунеться
Видалити за значеннямspysok.remove(5)зникне перша п'ятірка, інші залишаться
Видалити за номеромspysok.pop(0)елемент видалено і повернуто як результат
Скільки елементівlen(spysok)число
Відсортуватиspysok.sort()змінює сам список, нічого не повертає
Перевернути порядокspysok.reverse()порядок став зворотним
Знайти номерspysok.index(5)номер першої п'ятірки
Порахувати входженняspysok.count(5)скільки разів зустрілася п'ятірка
Чи є таке значення5 in spysokTrue або False

Два рядки з цієї таблиці варті окремої згадки, бо саме на них спотикаються. Метод sort() нічого не повертає — він перебудовує список на місці. Запис ocinky = ocinky.sort() виглядає логічно і вбиває дані: у змінній залишається None, а список попередньо таки відсортувався, тільки дістати його вже нізвідки. Якщо потрібна відсортована копія, а оригінал має вціліти, беріть sorted(ocinky) — це інша функція, і вона саме повертає новий список.

Друге — remove() прибирає тільки перше збігання. У списку [5, 5, 5] після remove(5) залишиться дві п'ятірки, а не нуль. Дитина, яка цього не знає, пише цикл, який «не дочищає» список, і потім шукає помилку в циклі, хоча цикл правильний.

Словник: значення за ім'ям

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

gravec = {'imia': 'Olia', 'ochky': 120, 'riven': 3}
print(gravec['ochky'])        # 120
gravec['ochky'] = 150         # замінили
gravec['zhyttia'] = 3         # додали новий ключ

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

Що робимоЯкЩо станеться
Прочитати значенняd['imia']значення або помилка KeyError
Прочитати безпечноd.get('imia')значення або None, без падіння
Значення за замовчуваннямd.get('ochky', 0)нуль, якщо ключа немає
Додати або замінитиd['riven'] = 4одна команда на обидва випадки
Видалити ключd.pop('riven')ключ зник, значення повернулося
Чи є ключ'imia' in dTrue або False
Усі ключіd.keys()перелік імен
Усі значенняd.values()перелік значень
Пари разомd.items()для обходу циклом
Злити два словникиd.update(inshyi)спільні ключі перезапишуться

Найкорисніший рядок тут — get(). Звернення d['ochky'] до відсутнього ключа зупиняє програму з KeyError, і для дитини це виглядає як поломка на рівному місці: вчора працювало, сьогодні ні, а насправді просто змінилися дані. Той самий d.get('ochky', 0) у цій ситуації віддає нуль, і програма йде далі. На заняттях ми показуємо get() одразу після квадратних дужок, щоб звичка з'явилася раніше за першу аварію.

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

for kliuch, znachennia in gravec.items():
    print(kliuch, '=', znachennia)

Якщо цикл написати без items(), тобто for k in gravec, він пройде тільки по ключах. Це не помилка, а інша задача: так роблять, коли значення не потрібні, наприклад щоб перевірити набір полів.

Зрізи: беремо частину списку

Окремо від таблиці стоїть запис із двокрапкою всередині квадратних дужок — зріз. Він повертає не один елемент, а новий список із кількох, і це найшвидший спосіб узяти «перші три» або «усе, крім останнього», неписавши жодного циклу.

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

Головне про зріз: права межа не входить. Запис [1:3] дає елементи з номерами один і два, а третій залишається за бортом. Логіка в тому, що різниця меж одразу дає довжину результату: три мінус один — два елементи. Звикається швидко, але перші кілька разів відповідь виглядає обрізаною на один.

Друга корисна властивість: зріз завжди створює копію. Тому kopiia = chysla[:] — найкоротший спосіб отримати окремий список, який можна псувати, не зачіпаючи оригінал. Просте kopiia = chysla копії не робить: обидва імені вказують на той самий список, і зміна через одне ім'я видна через інше. Це джерело половини «загадкових» помилок у перших іграх, де список монет чомусь змінюється сам.

Коли структури вкладаються одна в одну

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

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

Читається це справа наліво: gravci[0]['imia'] означає «у нульового гравця взяти поле імені». Дитина, яка один раз зібрала таку структуру сама, далі вже не плутає квадратні дужки з фігурними, бо бачить, що кожні відповідають за своє.

Коли список, а коли словник

Правило коротке. Якщо значення відповідають на питання «котрий за порядком» — беріть список. Якщо на питання «чий, якого саме» — словник.

ЗадачаСтруктураЧому
Оцінки за контрольні поспільсписокважливий порядок, імена не потрібні
Кількість життів у кожного гравцясловникзвертаємося за іменем гравця
Черга ходів у грісписокперший, другий, третій
Налаштування програмисловнику кожного параметра своє ім'я
Скільки разів зустрілося словословникслово — ключ, кількість — значення

Ознака, що структуру обрано невдало: щоб дістати потрібне значення, доводиться писати цикл з перевіркою. Якщо в списку лежать пари на кшталт ['Olia', 120] і для оцінок Олі ви щоразу перебираєте весь список — це словник, записаний списком. Перекладіть у словник, і цикл зникне разом з помилками в ньому.

Де спотикаються найчастіше

Три речі, які ми бачимо на заняттях з тижня в тиждень. Перша: нумерація з нуля і спроба звернутися до spysok[len(spysok)]. Останній елемент — це len - 1, або просто spysok[-1], і другий запис коротший та надійніший, бо не зламається, коли список подовшає.

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

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

Обидві структури зустрічаються в кожній наступній темі — від обробки файлів до роботи з API, тому зайве заняття на них вигідніше, ніж повертатися потім. Як вони виглядають у циклі for, ми розбирали окремо, а типові помилки новачків зібрані у своєму довіднику. Що з цього складається за перший рік — у матеріалі про проєкти першого року.

Найкоротший спосіб перевірити, що тема засвоєна: попросіть дитину переписати будь-яку свою стару програму зі списку на словник або навпаки. Якщо вона може сказати, що саме стало зручніше, а що гірше, вибір структури вона вже робить свідомо.