Список і словник — два перші «контейнери», з якими дитина стикається на курсі 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 spysok | True або 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 d | True або 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, ми розбирали окремо, а типові помилки новачків зібрані у своєму довіднику. Що з цього складається за перший рік — у матеріалі про проєкти першого року.
Найкоротший спосіб перевірити, що тема засвоєна: попросіть дитину переписати будь-яку свою стару програму зі списку на словник або навпаки. Якщо вона може сказати, що саме стало зручніше, а що гірше, вибір структури вона вже робить свідомо.