У попередній темі ти вже запустив свою першу програму й побачив, що інтерпретатор виконує рядки один за одним. Тепер розберемо те, з чого складається будь-яка програма взагалі: дані та імена для них. Тема виглядає елементарною — рівно доти, доки твоя програма не почне змінювати список, якого ти не чіпав. Тоді виявиться, що модель «коробки зі значенням» у голові була хибною, і чинити доведеться не код, а саме уявлення.
Тож почнімо одразу з правильної моделі, а не з тієї, яку потім треба переучувати.
01 / ПрисвоєнняЩо насправді робить знак «=»
Напишімо найкоротшу можливу програму:
Звична пояснювальна картинка така: є коробка з написом повідомлення,
у неї поклали текст. Забудь її. У Python відбувається інше, і саме в цьому порядку:
- Спершу праворуч від
=обчислюється вираз. У памʼяті зʼявляється обʼєкт — рядок «привіт». Він існує сам по собі й нічого не знає про імена. - Потім імʼя
повідомленняприкріплюється до цього обʼєкта, як наліпка на валізу.
Різниця не філософська. Коробка вміщує одне значення, і два імені — це дві коробки. Ярлик же можна почепити на вже наявний обʼєкт, і тоді два імені вкажуть на один обʼєкт. Усе, що ти зробиш через одне імʼя, миттєво «побачить» друге.
Саме тому b = a — це не копіювання. Це другий ярлик. Пройди інтерактив
крок за кроком і подивись, скільки обʼєктів насправді створено.
Інтерактив 1 · Імена як ярлики
Натискай «наступний крок». Ліворуч — імена, праворуч — обʼєкти в памʼяті. Стрілка = ярлик.
b = a обʼєкт лишився
один, а стрілок на нього стало дві. Тому b.append(3) змінює те, що
бачить і a. А c — уже інший обʼєкт з таким самим вмістом:
a == c дає True, a is c — False.02 / ОбʼєктТип, значення, ідентичність
Якщо імена — лише ярлики, то вся суть у тому, на що вони чіпляються. У Python усе є обʼєктом: число, рядок, список, навіть функція й сам тип. І кожен обʼєкт має рівно три речі:
| властивість | що це | як побачити |
|---|---|---|
| тип | до якого сорту належить обʼєкт і що з ним можна робити | type(x) |
| значення | власне вміст: число 7, текст, елементи списку | print(x) |
| ідентичність | «який саме» це обʼєкт — його особистість | id(x) |
Перші дві зрозумілі. Третя — найважливіша для цієї теми. Ідентичність (identity) — це відповідь на питання «це той самий обʼєкт чи інший, просто схожий?». Два різні аркуші з однаковим текстом мають однакове значення, але це два аркуші.
Функція id(x) повертає ціле число — унікальний номер обʼєкта, поки той
живий. Він не змінюється за все життя обʼєкта. Тому перевірка «чи це той самий обʼєкт»
зводиться до порівняння id(a) == id(b).
id() повертає буквально адресу обʼєкта в памʼяті. Це деталь реалізації, а не
обіцянка мови: в інших реалізаціях (PyPy, Jython) число може означати щось інше.
Звідси й наслідок: після знищення обʼєкта його номер може дістатися новому обʼєкту.
Порівнювати збережені «старі» id() з новими — надійний спосіб отримати
загадковий баг.03 / ТипізаціяТип живе в обʼєкті, а не в імені
У багатьох мовах тип оголошують разом зі змінною: «ось ціле число n, і
нічого, крім цілого, туди не покласти». Python влаштований інакше. Імʼя не має типу
взагалі — воно просто стрілка. Тип має обʼєкт на іншому кінці стрілки.
Тому одне й те саме імʼя може сьогодні вказувати на число, а через рядок — на список, і мова не заперечить. Це і називають динамічною типізацією. Покрути повзунок: імʼя лишається те саме, а тип змінюється разом з обʼєктом.
Інтерактив 2 · Одне імʼя, різні обʼєкти
Повзунок міняє те, що стоїть праворуч від =. Дивись, у кого змінюється тип.
значення
однакове для всіх шести випадків, у нього немає й не може бути типу. Праворуч щоразу
інший обʼєкт: інший тип, інший номер, інші правила гри.Динамічна типізація дає свободу, але не скасовує типів. Спроба додати число до рядка не «якось приведеться», а зупинить програму:
Це нормально й навіть добре: помилка спіймана одразу, з точною вказівкою, що з чим
не склеїлось. Правильно так: "вік: " + str(30).
04 / НезмінністьНезмінні та змінювані обʼєкти
Тепер найважливіший поділ у всій темі. Обʼєкти Python бувають двох сортів:
- Незмінні (immutable):
int,float,str,tuple,bool,None. Створений обʼєкт уже ніколи не зміниться. Число 5 назавжди лишиться пʼятіркою. - Змінювані (mutable):
list,dict,setі більшість власних класів. Такий обʼєкт можна відредагувати зсередини, не створюючи нового.
Звідси головний наслідок. Коли ти пишеш y = y + 1, число не змінюється —
не може змінитися. Python рахує y + 1, отримує новий
обʼєкт і перечіпляє на нього ярлик y. Старий обʼєкт стоїть неторканий, і
якщо на нього дивиться ще чиєсь імʼя, воно нічого не помітить.
А коли ти пишеш список.append(6), нічого нового не створюється: змінюється
сам обʼєкт. Ярлики нікуди не рухаються — але всі, хто на нього вказує, бачать нове
значення. Ось ті самі три кроки для числа й для списку поруч:
Інтерактив 3 · Незмінний проти змінюваного
Ліворуч число, праворуч список. Дії однакові — результат протилежний.
tuple незмінний — але лише
на один рівень. Кортеж пара = ([1, 2], "текст") не дозволить замінити свої
елементи, проте список усередині нього змінити можна:
пара[0].append(3) спрацює. Незмінність кортежа означає «набір посилань
зафіксовано», а не «все всередині заморожено».05 / Порівнянняis проти ==
Два оператори, які новачки плутають щотижня, а модель ярликів робить очевидними:
a == bпитає про значення: «вміст однаковий?»a is bпитає про ідентичність: «це фізично один і той самий обʼєкт?» Це буквально те саме, щоid(a) == id(b).
Два списки з однаковими числами дають == True і is False.
Правило просте: у 99% випадків тобі потрібен ==.
Оператор is застосовують майже виключно для перевірки на
None: if результат is None. Так пишуть, бо None
існує в програмі в єдиному примірнику, і порівняння за ідентичністю тут і точніше,
і швидше.
Інтерактив 4 · Однакові чи ті самі
Угорі — обрана пара імен. Унизу — кеш малих цілих чисел: пересунь повзунок через 256.
a = n і b = n результат
a is b залежить від самого числа. CPython наперед створює всі цілі від
−5 до 256 і роздає їх повторно — тому дрібні числа виявляються «тими
самими». Це оптимізація, а не правило мови.a is b для двох
однакових чисел — випадковість реалізації. Межа 256 може змінитися, а той самий код у
файлі й у REPL дасть різні відповіді: усередині одного блоку коду компілятор ще й склеює
однакові сталі. Порівнюєш числа чи рядки — пиши ==. Завжди.06 / ОбмінПомінятися значеннями
Класична задача: у a лежить одне, у b — інше, треба поміняти
місцями. Наївна спроба «в лоб» ламається, і модель ярликів пояснює чому: після
a = b перший обʼєкт лишається без жодного імені й губиться назавжди.
Рятує третє імʼя — воно притримує обʼєкт, поки той не знадобиться. Але в Python є
коротший спосіб: a, b = b, a. Спершу обчислюється весь правий бік — Python
збирає з нього тимчасовий кортеж, — і лише потім роздає імена. Тому нічого загубитись не
встигає. Перемикай режими й дивись, куди дінеться одиниця:
Інтерактив 5 · Три способи обміняти значення
Стартуємо з a = 1, b = 2. Мета — щоб стало навпаки.
a = 2,
b = 1. У третьому обидва імені осідають на двійці, а одиниця
зникає — її вже нічим не дістати.07 / ІменаЯк називати й чому це не дрібниця
Правила мови короткі: імʼя складається з літер, цифр і підкреслення, не починається з
цифри, регістр важливий (ціна й Ціна — різні імена). Українські
літери дозволені, і ціна = 100 — цілком робочий код.
Далі йдуть домовленості спільноти, і вони важать більше за правила:
- Імена словами.
середня_ціна, а неscі неtmp2. Код читають у десятки разів частіше, ніж пишуть; на розшифрування скорочення через місяць піде більше часу, ніж ти зекономив на друкуванні. - Малими літерами через підкреслення:
кількість_учнів,total_price. Такий стиль зветься snake_case і закріплений у стандарті оформлення коду PEP 8. - Не перекривай вбудовані імена.
list = [1, 2]не помилка — і в цьому біда: після такого рядка вбудована функціяlist()у цьому файлі перестане працювати. Так само небезпечніstr,dict,sum,id,type. - Одна мова на проєкт. Мішанина
сума_totalдратує сильніше, ніж будь-який з варіантів окремо.
Використати імʼя до присвоєння не вийде — Python не має «порожніх» імен за замовчуванням:
Читається буквально: такого ярлика ще ні на чому не висить. Найчастіша причина — одруківка в імені, друга за частотою — спроба скористатися результатом до того, як його порахували.
08 / ВидаленняЩо робить del
Команда del a звучить як «видалити обʼєкт», але робить рівно протилежне до
очікуваного: вона відчіпляє ярлик. Обʼєкт при цьому може лишитися
жити — якщо на нього вказує ще хтось.
Хто ж тоді прибирає сміття? CPython рахує, скільки посилань веде на кожен обʼєкт. Це підрахунок посилань (reference counting). Прикріпили нове імʼя — лічильник +1; відчепили — −1. Щойно лічильник падає до нуля, памʼять звільняється негайно, без жодної паузи й без окремого «збирача». Пройди чотири кроки й подивись на лічильник:
Інтерактив 6 · Лічильник посилань і del
Число в кутку обʼєкта — скільки імен на нього вказує.
del a на третьому кроці не знищив список —
на нього ще дивилось b. Обʼєкт помер лише тоді, коли зникло
останнє імʼя. Те саме відбувається без del: щойно ти перечіпляєш
ярлик на інший обʼєкт, старий втрачає посилання.gc), який періодично обходить памʼять і знаходить недосяжні групи
обʼєктів. Функція sys.getrefcount(x) покаже поточний лічильник — тільки памʼятай,
що вона сама на мить створює зайве посилання, тому число завжди на одиницю більше
за очікуване.09 / ПасткаСпільний список, який памʼятає все
Наостанок — найвідоміша пастка Python, і вона повністю пояснюється ярликами. Забігаючи наперед: функція — це спосіб дати імʼя шматку коду, щоб виконувати його багато разів (докладно про них буде далі в курсі). Аргументу можна задати значення за замовчуванням. І ось тут:
Виглядає невинно: «якщо кошика не передали — візьми порожній». Насправді список
[] створюється один раз, коли Python читає рядок
def, і назавжди залишається прикріпленим до самої функції. Кожен виклик без
аргументу отримує не новий порожній список, а той самий — уже з усім, що накидали
попередні виклики. Натисни «викликати» тричі:
Інтерактив 7 · Змінюване значення за замовчуванням
р1, р2, р3 — те, що повернули перший, другий і третій виклики.
Ліки стандартні: значенням за замовчуванням ставлять None — незмінний
одинак, який неможливо зіпсувати, — а порожній список створюють усередині функції, на
кожен виклик свій. Загальне правило звучить так: змінюваний обʼєкт ніколи не
має бути значенням за замовчуванням.
10 / ПідсумокЩо забрати з теми
Уся тема тримається на одному реченні: імʼя вказує на обʼєкт, а не вміщує його. З нього випливає решта:
- Присвоєння не копіює.
b = aдодає ярлик, а не обʼєкт. Щоб отримати справжню копію списку, потрібно сказати про це прямо:list(a)абоa.copy(). - Тип належить обʼєкту. Імʼя нетипізоване й може вказувати сьогодні на число, завтра на словник.
- Незмінне змінити неможливо. Будь-яка «зміна» числа, рядка чи кортежа — це насправді новий обʼєкт і перечеплений ярлик.
- Змінюване змінюється в місці. І це бачать усі імена, що на нього вказують, — звідси спільні списки, спільні словники й половина всіх дивних багів.
==про значення,isпро ідентичність. Друге бережи дляNone.
Далі буде цікавіше: у темі 04 візьмемо найпростіший на вигляд тип —
числа — і зʼясуємо, чому 0.1 + 0.2 не дорівнює 0.3 навіть
у бездоганно написаній програмі. Ти вже бачив цей результат в інтерактиві 4; тепер
розберемо, звідки він береться.
practice.ipynb ти передбачиш
результат восьми присвоєнь до запуску, звіриш свої відповіді з id() до і
після зміни, зловиш пастку спільного списку власними руками й навчишся робити чесну
копію.Далі в темі
Теорію прочитано. Тепер закріпи її на практиці.