Перейти до змісту

Урок 4. Умови та цикли: як програма приймає рішення

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

Що потрібно з уроку 3: змінні, типи int, float, str, перетворення int() і str(), різниця між 42 і "42".

Після уроку ти зможеш:

  • записувати умови через порівняння ==, !=, <, <=, >, >= і поєднувати їх через and, or, not;
  • обирати одну дію з кількох через if / elif / else і пояснювати, чому порядок перевірок важливий;
  • відрізняти «значення немає» (None) від нуля та порожнього рядка;
  • отримувати дані від користувача через input() і готувати їх до перевірки;
  • повторювати дії циклом while, змінювати стан і вчасно зупиняти цикл через break або continue.

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

Ноутбук заняття: Відкрити вправи в Colab Переглянути розв’язки

Пригадай

Дай відповідь подумки або на папері, нічого не запускаючи:

  1. Який тип поверне type(3.14)?
  2. Скільки буде int("7") + int("3")?
  3. str(5) == "5" — це True чи False?
Відповіді
  1. <class 'float'> — у числа є дробова частина.
  2. 10. Обидва рядки спершу перетворено на числа, тому + додає, а не склеює (без int() вийшло б "73").
  3. True. str(5) дає рядок "5", і порівнюються два однакові рядки.

True, False і порівняння

Кожне рішення програми починається з питання, на яке є лише дві відповіді: так або ні. Для цього в Python є тип bool з двома значеннями: True (істина) і False (хиба). Пишуться вони з великої літери.

Такі значення найчастіше отримують порівнянням:

Оператор Значення Приклад → результат
== дорівнює 500 == 500 → True
!= не дорівнює "кава" != "чай" → True
< менше 150 < 200 → True
<= менше або дорівнює 200 <= 200 → True
> більше 150 > 200 → False
>= більше або дорівнює 500 >= 500 → True
order_total = 220
print(order_total >= 200)
print(type(order_total >= 200))
True
<class 'bool'>

Результат порівняння — звичайне значення типу bool. Його можна надрукувати, записати в змінну і, головне, використати в умові.

= і == — різні речі

Один знак = присвоює: зв'язує ім'я зі значенням. Два знаки == порівнюють і повертають True або False. Якщо переплутати їх в умові, Python не запустить програму:

if order_total = 500:
    print("Рівно 500")
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?

Порівняння рядків враховує регістр: "Кава" == "кава" дає False. Це знадобиться, коли будемо обробляти введення користувача.

Ланцюжок порівнянь

Щоб перевірити, чи потрапляє значення в діапазон, у Python можна записати два порівняння одним ланцюжком:

age = 30
print(18 <= age < 65)
True

Запис 18 <= age < 65 означає те саме, що 18 <= age and age < 65: Python порівнює кожну сусідню пару.

Передбач результат, перш ніж відкрити відповідь:

print(1 == 2 < 3)
Відповідь
False

Ланцюжок читається як 1 == 2 and 2 < 3. Перша частина 1 == 2 хибна, тому весь вираз — False. Це не (1 == 2) < 3.

Ще одна пара, яку варто передбачити самому:

print(1 < 3 > 2)
print((1 < 3) > 2)
Відповідь
True
False

Без дужок це ланцюжок: 1 < 3 and 3 > 2 — обидві частини істинні. З дужками спершу обчислюється (1 < 3), тобто True, а далі True > 2. True поводиться як 1 (див. примітку нижче), і 1 > 2 хибне. Дужки перетворили ланцюжок на два окремі порівняння.

Два уточнення

  • bool — підтип int: True поводиться як 1, а False — як 0. Тому True + True дає 2, а 0 == False дає True. У звичайному коді на цьому не будують логіку, але це пояснює деякі несподівані результати.
  • Не складай ланцюжки з !=. Вираз "cat" != "dog" != "cat" дає True, хоча перше і третє значення однакові: перевіряються лише сусідні пари.

Парне чи непарне: остача від ділення

Оператор % з уроку 3 дає остачу від ділення. Число парне, коли остача від ділення на 2 дорівнює нулю:

for number in [10, 7, -7]:
    print(number, number % 2 == 0)
10 True
7 False
-7 False

Цикл for лише перебирає три приклади (урок 6). Для -7 Python дає -7 % 2 == 1: остача має знак дільника, тому для від'ємних непарних чисел вона теж 1. Надійна перевірка непарності — number % 2 != 0, вона працює для будь-яких цілих.

Умови: if, elif, else

Одна умова

Повернімося до кафе. Доставка можлива лише для замовлень від 200 грн:

order_total = 220

if order_total >= 200:
    print("Доставку можна оформити")

print("Перевірку завершено")
Доставку можна оформити
Перевірку завершено

Як це виконується:

  1. Python обчислює умову order_total >= 200 і отримує True.
  2. Оскільки умова істинна, виконується блок — рядки з відступом під if.
  3. Рядок print("Перевірку завершено") не має відступу, тому він не належить до if і виконується завжди.

Якби order_total дорівнював 150, програма надрукувала б лише «Перевірку завершено».

Відступ — це не оформлення, а частина мови: саме він показує, які рядки належать до блоку. Стандарт — 4 пробіли. Після двокрапки блок обов'язковий:

if order_total >= 200:
print("Доставку можна оформити")
IndentationError: expected an indented block after 'if' statement on line 1

Кілька варіантів: elif і else

Тепер правило доставки складніше:

  • від 500 грн — безкоштовно;
  • від 200 грн — 60 грн;
  • менше — доставка недоступна.
order_total = 220

if order_total >= 500:
    print("Доставка: безкоштовно")
elif order_total >= 200:
    print("Доставка: 60 грн")
else:
    print("Мінімальне замовлення для доставки — 200 грн")
Доставка: 60 грн

elif означає «інакше, якщо», else — «в усіх інших випадках». Python перевіряє умови зверху вниз і виконує першу гілку, умова якої істинна. Після цього решта гілок пропускається, їхні умови навіть не обчислюються. Якщо жодна умова не спрацювала, виконується else.

flowchart TD
    classDef step     fill:#eceff1,stroke:#546e7a,stroke-width:1px,color:#1a1a1a;
    classDef decision fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#1a1a1a;
    classDef success  fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1a1a1a;
    classDef error    fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#1a1a1a;
    classDef warning  fill:#fff8e1,stroke:#e65100,stroke-width:2px,color:#1a1a1a;

    S["order_total"] --> C1{"order_total >= 500?"}
    C1 -- так --> A1["Доставка: безкоштовно"]
    C1 -- ні --> C2{"order_total >= 200?"}
    C2 -- так --> A2["Доставка: 60 грн"]
    C2 -- ні --> A3["else: мінімальне<br>замовлення 200 грн"]
    A1 --> E["далі програма"]
    A2 --> E
    A3 --> E

    class S,E step
    class C1,C2 decision
    class A1,A2 success
    class A3 warning

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

Ось як проходить той самий код для різних сум:

order_total Перевірки Що виведе програма
150 >= 500 — ні, >= 200 — ні Мінімальне замовлення для доставки — 200 грн
200 >= 500 — ні, >= 200 — так Доставка: 60 грн
499 >= 500 — ні, >= 200 — так Доставка: 60 грн
500 >= 500 — так (далі не перевіряється) Доставка: безкоштовно
800 >= 500 — так (далі не перевіряється) Доставка: безкоштовно

Зверни увагу на межі 200 і 500: оператор >= включає саму межу, тому замовлення рівно на 500 грн отримує безкоштовну доставку.

Передбач результат: порядок перевірок

Хтось поміняв умови місцями:

order_total = 800

if order_total >= 200:
    print("Доставка: 60 грн")
elif order_total >= 500:
    print("Доставка: безкоштовно")
else:
    print("Мінімальне замовлення для доставки — 200 грн")
Що виведе програма для 800 грн?

Пройди умови зверху вниз і зупинись на першій істинній.

Відповідь і пояснення
Доставка: 60 грн

800 >= 200 істинна вже на першій перевірці, тому виконується перша гілка, а elif order_total >= 500 не перевіряється зовсім. Гілка безкоштовної доставки стала недосяжною: будь-яке число, більше за 500, більше й за 200.

Правило: коли умови перевіряють межі одного значення, починай із найвужчої (найбільшої межі) — або записуй кожен діапазон повністю: 200 <= order_total < 500.

Окремі if замість elif

Якщо написати кожну перевірку окремим if, Python перевірить усі:

order_total = 600

if order_total >= 500:
    print("Безкоштовна доставка")
if order_total >= 200:
    print("Доставка 60 грн")
Безкоштовна доставка
Доставка 60 грн

Клієнт отримав два суперечливі повідомлення. Окремі if доречні, коли перевірки незалежні (наприклад, «чи є коментар» і «чи потрібні прибори»). Якщо з кількох варіантів має спрацювати лише один — потрібен ланцюжок if / elif / else.

Вкладені умови

Усередині блоку може бути ще одна умова. Тоді її блок має подвійний відступ:

order_total = 150
is_delivery = True

if is_delivery:
    if order_total >= 200:
        print("Оформлюємо доставку")
    else:
        print("Для доставки не вистачає", 200 - order_total, "грн")
else:
    print("Замовлення в залі")
Для доставки не вистачає 50 грн

Внутрішня умова перевіряється лише тоді, коли зовнішня (is_delivery) істинна. Кожен else належить тому if, під яким він стоїть з тим самим відступом.

Шлях виконання для order_total = 150, is_delivery = True. Помаранчеві вузли — перевірки, які справді виконались, зелений — гілка, що спрацювала, червоні — гілки, до яких виконання не дійшло:

flowchart TD
    classDef step     fill:#eceff1,stroke:#546e7a,stroke-width:1px,color:#1a1a1a;
    classDef decision fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#1a1a1a;
    classDef success  fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1a1a1a;
    classDef error    fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#1a1a1a;
    classDef warning  fill:#fff8e1,stroke:#e65100,stroke-width:2px,color:#1a1a1a;

    S["order_total = 150<br>is_delivery = True"] --> O{"is_delivery?<br>True"}
    O -- так --> I{"order_total >= 200?<br>150 >= 200 → False"}
    O -. ні .-> H["Замовлення в залі"]
    I -. так .-> D["Оформлюємо доставку"]
    I -- ні --> M["Для доставки не вистачає<br>50 грн"]

    class S step
    class O,I warning
    class M success
    class H,D error

Зовнішня перевірка відкриває або закриває вхід до внутрішньої: при is_delivery = False умова order_total >= 200 навіть не обчислювалась би.

match / case: вибір за значенням

Коли одна змінна порівнюється з кількома конкретними значеннями, довгий ланцюжок elif command == ... можна записати коротше. З Python 3.10 для цього є match:

for mode in ["доставка", "в залі", "таксі"]:
    match mode:
        case "зал" | "в залі":
            print("Замовлення в залі")
        case "доставка":
            print("Оформлюємо доставку")
        case "самовивіз":
            print("Заберіть на касі")
        case _:
            print("Невідомий спосіб:", mode)
Оформлюємо доставку
Замовлення в залі
Невідомий спосіб: таксі

Цикл for тут лише перебирає три приклади (його розберемо в уроці 6). Головне — блок match:

  • match mode: — яке значення перевіряємо;
  • case "доставка": — гілка спрацює, якщо mode == "доставка";
  • | — «або»: case "зал" | "в залі": спрацює на будь-якому з двох рядків;
  • case _: — «усе інше», як else;
  • виконується перша гілка, що підійшла, решта пропускається — як у ланцюжку elif.
flowchart LR
    classDef step     fill:#eceff1,stroke:#546e7a,stroke-width:1px,color:#1a1a1a;
    classDef decision fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#1a1a1a;
    classDef success  fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1a1a1a;
    classDef error    fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#1a1a1a;
    classDef warning  fill:#fff8e1,stroke:#e65100,stroke-width:2px,color:#1a1a1a;

    V["mode = #quot;таксі#quot;"] --> C1{"#quot;зал#quot; | #quot;в залі#quot;?"}
    C1 -- ні --> C2{"#quot;доставка#quot;?"}
    C2 -- ні --> C3{"#quot;самовивіз#quot;?"}
    C3 -- ні --> C4["case _<br>Невідомий спосіб: таксі"]

    class V step
    class C1,C2,C3 decision
    class C4 success

case _, а не case other

Голе ім'я в case не порівнюється, а захоплює будь-яке значення: case other: спрацює завжди й запише значення у змінну other. Якщо після нього є ще гілки, Python не запустить програму: SyntaxError: name capture 'other' makes remaining patterns unreachable. Для «усього іншого» пиши case _:.

match уміє більше: розбирати списки й словники за формою, додавати умову if до гілки. Усе це — в окремому ноутбуці Відкрити вправи в Colab Переглянути розв’язки. Для порівнянь на кшталт order_total >= 500 залишайся з if / elif: match — про збіг із конкретними значеннями та формою даних.

Truthy, falsy і None

Значення як умова

В if можна поставити не лише порівняння, а будь-яке значення. Тоді Python сам вирішує, вважати його істинним чи хибним. Хибними (falsy) вважаються:

  • False і None;
  • нуль будь-якого числового типу: 0, 0.0;
  • порожні рядок і колекції: "", [] (списки — тема уроку 5).

Усе інше — truthy, тобто вважається істинним. Перевірити можна функцією bool():

print(bool(0), bool(0.0), bool(""), bool(None), bool([]))
print(bool("False"), bool("0"), bool(" "))
False False False False False
True True True

Другий рядок часто дивує: "False", "0" і навіть рядок з одного пробілу — непорожні рядки, тому вони істинні. Python не читає зміст рядка.

Як Python вирішує, чи виконати блок if value::

flowchart TD
    classDef step     fill:#eceff1,stroke:#546e7a,stroke-width:1px,color:#1a1a1a;
    classDef decision fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#1a1a1a;
    classDef success  fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1a1a1a;
    classDef error    fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#1a1a1a;
    classDef warning  fill:#fff8e1,stroke:#e65100,stroke-width:2px,color:#1a1a1a;

    V["if value:"] --> N{"False або None?"}
    N -- так --> F["falsy<br>блок пропускається"]
    N -- ні --> Z{"нуль?<br>0, 0.0"}
    Z -- так --> F
    Z -- ні --> E{"порожнє?<br>#quot;#quot;, []"}
    E -- так --> F
    E -- ні --> T["truthy<br>блок виконується"]

    class V step
    class N,Z,E decision
    class F error
    class T success

Рядок "0" проходить усі три перевірки з відповіддю «ні»: це не None, не число і не порожній рядок.

Так зручно перевіряти, чи ввів користувач хоч щось:

comment = ""

if comment:
    print("Коментар:", comment)
else:
    print("Без коментаря")
Без коментаря

bool() не розуміє відповідь «так/ні»

Якщо користувач ввів "ні", то bool("ні") дає True, бо рядок непорожній. Щоб перевірити відповідь, порівнюй текст: answer == "так".

None — «значення немає»

None — окреме значення, яке означає «значення відсутнє»: ще не відомо, не вказано. Це не нуль і не порожній рядок:

print(None == 0, None == "")
False False

Перевіряють None через is None або is not None.

None має власний тип — NoneType, і це єдине значення цього типу:

print(type(None), bool(None))
<class 'NoneType'> False

None — falsy, як 0 і "", але означає інше: не «нуль», а «нічого не вказано».

У кафе це важливо для чайових: 0 означає «гість свідомо відмовився», а None — «гість ще нічого не вказав». Програма має відрізняти ці випадки:

tip_percent = None

if tip_percent is None:
    print("Чайові не вказано")
elif tip_percent == 0:
    print("Гість відмовився від чайових")
else:
    print("Чайові:", tip_percent, "%")
tip_percent Результат
None Чайові не вказано
0 Гість відмовився від чайових
10 Чайові: 10 %

Типова помилка

Умова if not tip_percent: спрацює і для None, і для 0, бо обидва значення falsy. Коли нуль — допустиме значення, перевіряй саме відсутність: if tip_percent is None:.

and, or, not

Поєднання умов

  • a and b — істинно, коли істинні обидві умови;
  • a or b — істинно, коли істинна хоча б одна;
  • not a — заперечення: True стає False і навпаки.

Студентська знижка діє, лише коли гість — студент і сума від 300 грн:

is_student = True
order_total = 320

if is_student and order_total >= 300:
    print("Знижка 10 %")
Знижка 10 %

Порядок обчислення

Спершу обчислюються порівняння, потім not, потім and, і лише потім or. Тому вираз

not is_student or order_total > 1000 and guests > 4

Python читає як (not is_student) or ((order_total > 1000) and (guests > 4)). Щоб читачеві не доводилось згадувати цей порядок, став дужки явно — результат не зміниться, а намір стане очевидним.

Найпідступніший випадок — not поруч із порівнянням. Кафе хоче показати банер «сьогодні тихо», коли не правда, що «гостей багато і сьогодні вихідний». Колега пише:

guests = 0
is_weekend = False

print(not guests > 10 and is_weekend)
print(not (guests > 10 and is_weekend))
Передбач обидва рядки

Де Python ставить дужки в першому виразі?

Відповідь
False
True

Перший рядок Python читає як (not (guests > 10)) and is_weekend: not чіпляється лише до порівняння, а and — до всього, що праворуч. True and False дає False, хоча гостей нуль і банер мав би з'явитися. Другий рядок записує задум явно: заперечуємо всю умову в дужках. Якщо not має стосуватися кількох умов — дужки обов'язкові.

Коротке обчислення

Python зупиняє обчислення, щойно результат відомий. Для and: якщо ліва частина хибна, права не обчислюється. Для or: якщо ліва частина істинна, права не обчислюється.

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

guests = 0
order_total = 300

if guests > 0 and order_total / guests > 200:
    print("Великий чек на гостя")
else:
    print("Середній чек не рахуємо")
Середній чек не рахуємо

guests > 0 хибна, тому ділення order_total / guests не виконується взагалі. Якщо поміняти частини місцями (order_total / guests > 200 and guests > 0), ділення відбудеться першим і програма впаде: ZeroDivisionError: division by zero.

Обидва порядки поруч, при guests = 0:

flowchart TD
    classDef step     fill:#eceff1,stroke:#546e7a,stroke-width:1px,color:#1a1a1a;
    classDef decision fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#1a1a1a;
    classDef success  fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1a1a1a;
    classDef error    fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#1a1a1a;
    classDef warning  fill:#fff8e1,stroke:#e65100,stroke-width:2px,color:#1a1a1a;

    subgraph OK["guests > 0 and order_total / guests > 200"]
        direction LR
        A1{"guests > 0<br>0 > 0 → False"} --> A2["and уже знає відповідь<br>False"]
        A2 --> A3["ділення не виконується<br>гілка else"]
    end
    subgraph BAD["order_total / guests > 200 and guests > 0"]
        direction LR
        B1["order_total / guests<br>300 / 0"] --> B2["ZeroDivisionError<br>програма падає"]
    end
    OK ~~~ BAD

    class A1 warning
    class A2 step
    class A3 success
    class B1 warning
    class B2 error

Ліва частина and працює як охоронець: права частина обчислюється, лише коли ліва істинна.

and і or повертають операнд

not завжди повертає True або False. А and і or повертають один зі своїх операндів, не обов'язково bool:

  • x or y повертає x, якщо x truthy, інакше y;
  • x and y повертає x, якщо x falsy, інакше y.
print(2 and 3)
print(0 or 10)
print("" or "без коментаря")
print(not "")
3
10
без коментаря
True

В умові if це непомітно: Python однаково перевіряє, truthy чи falsy результат. Але якщо записати результат у змінну, отримаєш саме операнд.

Дві поширені пастки

1. or з голим рядком. Це виглядає як «відповідь так або т», але працює інакше:

answer = "ні"
if answer == "так" or "т":
    print("Підтверджено")

Програма надрукує «Підтверджено». Вираз читається як (answer == "так") or "т", а непорожній рядок "т" завжди truthy. Правильно — порівнювати обидва рази: answer == "так" or answer == "т".

2. or як «значення за замовчуванням», коли 0 допустимий.

supplied_tip = 0
tip = supplied_tip or 10
print(tip)

Виведе 10, хоча гість явно вказав 0: or не відрізняє нуль від відсутнього значення. Якщо «немає» позначено None, перевіряй саме його:

if supplied_tip is None:
    tip = 10
else:
    tip = supplied_tip

Дані від користувача: input()

input() показує підказку, чекає, поки користувач введе текст і натисне Enter, і повертає введене. Результат завжди рядок, навіть якщо ввели цифри:

guests_text = input("Скільки гостей? ")
print(type(guests_text))
Скільки гостей? 3
<class 'str'>

Щоб рахувати, рядок треба явно перетворити через int() або float(). Інакше + склеїть текст: "3" + "2" дає "32".

А спроба додати до введеного число зупинить програму:

guests_text = "3"          # так повертає input()
print(guests_text + 1)
TypeError: can only concatenate str (not "int") to str

TypeError каже: операція не підходить для цих типів. Рядок і число не додаються — спершу int(guests_text).

Очищення тексту

Люди вводять з пробілами й різним регістром: " Кава", "КАВА ". Перед порівнянням текст приводять до одного вигляду:

  • .strip() прибирає пробіли на початку й у кінці;
  • .lower() переводить у нижній регістр.
command = input("Позиція: ").strip().lower()
print(command == "кава")
Позиція:   КАВА
True

Методи виконуються зліва направо: спершу input() повертає " КАВА ", потім .strip() дає "КАВА", а .lower() — "кава".

Коли число не число

int() приймає лише рядок, схожий на ціле число (пробіли по краях він ігнорує: int(" 3 ") дає 3). На іншому тексті програма зупиняється:

int("два")    # ValueError: invalid literal for int() with base 10: 'два'
int("12.5")   # ValueError: invalid literal for int() with base 10: '12.5'

Кожен із цих рядків окремо зупиняє програму з ValueError: після першої помилки наступні рядки вже не виконуються.

Для "12.5" потрібен float("12.5"). Як перехоплювати такі помилки, розберемо в уроці 14. Поки що можна перевірити текст перед перетворенням:

guests_text = input("Скільки гостей? ").strip()

if guests_text.isdigit():
    guests = int(guests_text)
    print("Гостей:", guests)
else:
    print("Введи кількість цифрами, наприклад 3")
Скільки гостей? три
Введи кількість цифрами, наприклад 3

Межі цієї перевірки

.isdigit() повертає True, лише якщо рядок непорожній і кожен символ — цифра. Тому вона відкидає "-3", "12.5" і " 3" (пробіл — не цифра; тому спершу .strip()). Для кількості гостей це підходить. Від'ємні й дробові числа краще перевіряти через винятки — урок 14.

while і стан програми

Цикл із лічильником

Кухня готує три порції. Програма має повторити дію «приготувати порцію» потрібну кількість разів:

portions = 3
cooked = 0

while cooked < portions:
    cooked += 1
    print("Готова порція", cooked)

print("Усі порції готові")
Готова порція 1
Готова порція 2
Готова порція 3
Усі порції готові

cooked += 1 — коротший запис cooked = cooked + 1: збільшити значення на 1.

while перевіряє умову перед кожним повтором (ітерацією). Поки умова істинна, виконується блок; щойно вона стає хибною, програма переходить до першого рядка після циклу. Таблиця показує, як змінюється стан — значення змінних:

cooked Умова cooked < portions Що відбувається
0 0 < 3 → True cooked стає 1, друк «Готова порція 1»
1 1 < 3 → True cooked стає 2, друк «Готова порція 2»
2 2 < 3 → True cooked стає 3, друк «Готова порція 3»
3 3 < 3 → False вихід із циклу, друк «Усі порції готові»

Кожен цикл while складається з чотирьох частин: початковий стан → перевірка → дія → зміна стану.

flowchart TD
    classDef step     fill:#eceff1,stroke:#546e7a,stroke-width:1px,color:#1a1a1a;
    classDef decision fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#1a1a1a;
    classDef success  fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1a1a1a;
    classDef error    fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#1a1a1a;
    classDef warning  fill:#fff8e1,stroke:#e65100,stroke-width:2px,color:#1a1a1a;

    I["початковий стан<br>portions = 3, cooked = 0"] --> C{"cooked < portions?"}
    C -- так --> U["зміна стану<br>cooked += 1"]
    U --> P["дія<br>print('Готова порція', cooked)"]
    P --> C
    C -- ні --> D["Усі порції готові"]

    class I,P step
    class C decision
    class U warning
    class D success

Стрілка від дії повертається до перевірки: після кожного повтору Python знову обчислює cooked < portions. Вийти з циклу можна лише гілкою «ні», тому зміна стану (cooked += 1) — обов'язкова частина.

Та сама програма покроково: кожен блок — один прохід, у вузлах — стан змінних:

flowchart TD
    classDef step     fill:#eceff1,stroke:#546e7a,stroke-width:1px,color:#1a1a1a;
    classDef decision fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#1a1a1a;
    classDef success  fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1a1a1a;
    classDef error    fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#1a1a1a;
    classDef warning  fill:#fff8e1,stroke:#e65100,stroke-width:2px,color:#1a1a1a;

    subgraph P1["прохід 1"]
        direction LR
        C1{"0 < 3 → True"} --> U1["cooked = 1<br>Готова порція 1"]
    end
    subgraph P2["прохід 2"]
        direction LR
        C2{"1 < 3 → True"} --> U2["cooked = 2<br>Готова порція 2"]
    end
    subgraph P3["прохід 3"]
        direction LR
        C3{"2 < 3 → True"} --> U3["cooked = 3<br>Готова порція 3"]
    end
    subgraph P4["перевірка 4"]
        direction LR
        C4{"3 < 3 → False"} --> E["вихід з циклу<br>Усі порції готові"]
    end
    P1 --> P2 --> P3 --> P4

    class C1,C2,C3 decision
    class U1,U2,U3 step
    class C4 warning
    class E success

Умова перевіряється чотири рази, а блок виконується три: остання перевірка лише вирішує, що час виходити.

Нуль повторів

Якщо умова хибна з самого початку, блок не виконується жодного разу:

portions = 0
cooked = 0

while cooked < portions:
    cooked += 1
    print("Готова порція", cooked)

print("Усі порції готові")
Усі порції готові

0 < 0 хибна вже на першій перевірці. На відміну від деяких інших мов, у Python немає циклу, який «завжди виконується хоча б раз».

Помилка на одиницю

Один символ в умові змінює кількість повторів:

portions = 3
cooked = 0

while cooked <= portions:
    cooked += 1
    print("Готова порція", cooked)
Скільки порцій приготує кухня?

Склади таблицю, як вище, і дійди до перевірки, яка поверне False.

Відповідь
Готова порція 1
Готова порція 2
Готова порція 3
Готова порція 4

При cooked = 3 умова 3 <= 3 ще істинна, тому виконується четвертий повтор. Така помилка на одиницю (off-by-one) — одна з найчастіших у циклах. Перевіряй межі: що станеться на останньому допустимому значенні і на першому недопустимому.

Нескінченний цикл

Не запускай цей код без потреби

portions = 3
cooked = 0

while cooked < portions:
    print("Готова порція", cooked)

cooked ніколи не змінюється, тому 0 < 3 залишається істинною назавжди, і програма друкує той самий рядок без кінця. У терміналі зупини її клавішами Ctrl+C, у Jupyter чи Colab — кнопкою зупинки клітинки. Виправлення — повернути зміну стану cooked += 1.

Якщо цикл поводиться дивно, додай у блок тимчасовий print() зі значеннями змінних та умови. Так видно стан на кожному кроці.

break і continue

break — вийти з циклу

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

Це зручно, коли кількість повторів заздалегідь невідома. Офіціант вводить позиції, доки не напише готово:

while True:
    command = input("Позиція: ").strip().lower()
    if command == "готово":
        break
    print("Додано:", command)

print("Замовлення прийнято")

while True — умова, яка завжди істинна. Тому вийти з такого циклу можна лише через break. Без нього цикл був би нескінченним.

Нехай офіціант вводить кава, чай, готово. Програма виведе:

Позиція: кава
Додано: кава
Позиція: чай
Додано: чай
Позиція: готово
Замовлення прийнято

Покроково:

flowchart TD
    classDef step     fill:#eceff1,stroke:#546e7a,stroke-width:1px,color:#1a1a1a;
    classDef decision fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#1a1a1a;
    classDef success  fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1a1a1a;
    classDef error    fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#1a1a1a;
    classDef warning  fill:#fff8e1,stroke:#e65100,stroke-width:2px,color:#1a1a1a;

    subgraph R1["повтор 1"]
        direction LR
        I1["command = #quot;кава#quot;"] --> Q1{"== #quot;готово#quot;?<br>ні"} --> A1["Додано: кава"]
    end
    subgraph R2["повтор 2"]
        direction LR
        I2["command = #quot;чай#quot;"] --> Q2{"== #quot;готово#quot;?<br>ні"} --> A2["Додано: чай"]
    end
    subgraph R3["повтор 3"]
        direction LR
        I3["command = #quot;готово#quot;"] --> Q3{"== #quot;готово#quot;?<br>так"} --> B["break"]
    end
    R1 --> R2 --> R3 --> END["Замовлення прийнято"]

    class I1,I2,I3,A1,A2 step
    class Q1,Q2 decision
    class Q3,B warning
    class END success

Рядок print("Додано:", command) у третьому повторі вже не виконується: break виходить з циклу одразу.

while … else — варто знати, що таке буває

У циклу while може бути гілка else. Вона виконується, коли цикл завершився сам — умова стала хибною, — і не виконується, якщо вийшли через break:

attempts = 3
while attempts > 0:
    print("Спроба", attempts)
    attempts -= 1
else:
    print("Спроби закінчились")
Спроба 3
Спроба 2
Спроба 1
Спроби закінчились

Це зручно в пошуку: «знайшли — break, не знайшли за всі спроби — else». Трапляється while … else рідко, тому досить упізнавати його в чужому коді. Повний приклад — у задачі «Вгадай число» в розділі «Практика».

continue — до наступної перевірки

continue завершує поточний повтор: решта блоку пропускається, і while знову перевіряє умову. Коли офіціант вводить позицію, якої немає в меню, її не треба додавати до суми — лише попередити:

flowchart TD
    classDef step     fill:#eceff1,stroke:#546e7a,stroke-width:1px,color:#1a1a1a;
    classDef decision fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#1a1a1a;
    classDef success  fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1a1a1a;
    classDef error    fill:#ffebee,stroke:#c62828,stroke-width:3px,color:#1a1a1a;
    classDef warning  fill:#fff8e1,stroke:#e65100,stroke-width:2px,color:#1a1a1a;

    W{"while True"} --> IN["command = input(...)<br>.strip().lower()"]
    IN --> Q1{"command == 'готово'?"}
    Q1 -- так --> BR["break"]
    Q1 -- ні --> Q2{"позиція є в меню?"}
    Q2 -- ні --> CO["повідомлення<br>continue"]
    CO --> W
    Q2 -- так --> ADD["order_total += price<br>items += 1"]
    ADD --> W
    BR --> END["підсумок і доставка"]

    class IN,ADD step
    class W,Q1,Q2 decision
    class CO warning
    class BR,END success

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

Пастка: continue пропускає зміну стану

Кафе виводить вільні столики з 1 по 6. Столик 4 зарезервовано, його треба пропустити:

table = 0

while table < 6:
    table += 1
    if table == 4:
        continue
    print("Столик", table, "вільний")
Столик 1 вільний
Столик 2 вільний
Столик 3 вільний
Столик 5 вільний
Столик 6 вільний

Тепер той самий задум, але лічильник збільшується наприкінці блоку:

table = 1

while table <= 6:
    if table == 4:
        continue
    print("Столик", table, "вільний")
    table += 1
Що станеться, коли table дорівнюватиме 4?

Простеж, чи дійде виконання до рядка table += 1.

Відповідь

Програма надрукує столики 1, 2, 3 і зависне. Коли table == 4, continue одразу повертає до перевірки 4 <= 6, а рядок table += 1 не виконується. Стан більше не змінюється, і цикл стає нескінченним.

Виправлення: змінюй стан до continue, як у першій версії (table += 1 на початку блоку). Загальне правило: перед кожним continue перевір, чи оновилися змінні, від яких залежить умова циклу.

Читаємо помилки

Перш ніж виправляти код, прочитай останній рядок повідомлення про помилку: там тип помилки й пояснення. Три найчастіші в цьому уроці:

Повідомлення Що сталося Як виправити
SyntaxError: expected ':' після if, elif, else чи while забули двокрапку if order_total >= 500:
IndentationError: expected an indented block after 'if' statement on line 1 блок під умовою без відступу 4 пробіли перед кожним рядком блоку
ValueError: invalid literal for int() with base 10: 'два' int() отримав текст, який не є цілим числом перевір .isdigit() перед int()
TypeError: can only concatenate str (not "int") to str до рядка з input() додали число спершу int(...)

Python показує й номер рядка, де знайшов помилку. Для SyntaxError справжня причина іноді стоїть рядком вище: наприклад, незакрита дужка.

import і аргументи командного рядка

Мінімальний import

Іноді програмі потрібна готова функція, якої немає серед вбудованих. Наприклад, випадкове число для гри в розділі «Спробуй самостійно»:

import random

secret = random.randint(1, 20)

import random підключає модуль random — файл із готовими функціями для випадкових значень. Після цього до його функцій звертаються через крапку: random.randint(1, 20) повертає випадкове ціле число від 1 до 20, обидві межі включно. Як улаштовані модулі, розберемо в уроці 13.

sys.argv — слова після python файл.py

input() питає дані під час роботи програми. Є й другий спосіб — передати їх під час запуску, словами після імені файлу. Їх збирає модуль sys. Файл argv_demo.py:

import sys

print(sys.argv)
print(len(sys.argv))

Запуск у терміналі:

$ python argv_demo.py 87 привіт
['argv_demo.py', '87', 'привіт']
3
  • sys.argv[0] — ім'я самого файлу, sys.argv[1] — перше слово після нього, і так далі. Індексація — як у рядків з уроку 3.
  • Кожен елемент — рядок, навіть '87'. Правило те саме, що для input(): перевір .isdigit(), потім int().
  • len(sys.argv) — скільки всього слів разом з іменем файлу. Квадратні дужки у виводі означають список — тема уроку 5.

Програма має перевірити, що аргумент один і що це число, — інакше пояснити, як її запускати. Файл grade.py перетворює бал на оцінку (A — від 90, B — від 75, C — від 60, інакше F):

import sys

if len(sys.argv) != 2:
    print("Використання: python grade.py <бал>")
elif not sys.argv[1].isdigit():
    print("Бал має бути цілим числом, отримано:", sys.argv[1])
else:
    score = int(sys.argv[1])
    if score > 100:
        print("Бал не може бути більшим за 100")
    elif score >= 90:
        print("A")
    elif score >= 75:
        print("B")
    elif score >= 60:
        print("C")
    else:
        print("F")
$ python grade.py 87
B
$ python grade.py 120
Бал не може бути більшим за 100
$ python grade.py
Використання: python grade.py <бал>
$ python grade.py 87 90
Використання: python grade.py <бал>
$ python grade.py 8.5
Бал має бути цілим числом, отримано: 8.5

Зверни увагу на порядок: спершу перевіряємо кількість аргументів (інакше sys.argv[1] не існуватиме), потім — формат, і лише потім перетворюємо й працюємо з числом.

Рядок if __name__ == '__main__': — упізнай, коли побачиш

У чужих програмах основний код часто стоїть під умовою if __name__ == "__main__":. Вона означає: «виконати цей блок, лише коли файл запустили командою python файл.py, а не імпортували в іншу програму». Докладно — в уроці 13 про модулі; поки що досить знати, навіщо цей рядок.

Практика

Розібраний приклад: приймання замовлення

Зберемо все разом. Програма:

  • приймає позиції меню (кава — 55 грн, круасан — 70 грн, чай — 40 грн), доки офіціант не введе готово;
  • ігнорує регістр і пробіли, попереджає про невідомі позиції;
  • наприкінці показує кількість позицій, суму і правило доставки.
print("Приймаємо замовлення. Команди: кава, круасан, чай, готово")

order_total = 0
items = 0

while True:
    command = input("Позиція: ").strip().lower()

    if command == "готово":
        break

    if command == "кава":
        price = 55
    elif command == "круасан":
        price = 70
    elif command == "чай":
        price = 40
    else:
        print("Такої позиції немає:", command)
        continue

    order_total += price
    items += 1
    print("Додано:", command, "—", price, "грн. Разом:", order_total, "грн")

print("Позицій у замовленні:", items)
print("Сума:", order_total, "грн")

if order_total >= 500:
    print("Доставка: безкоштовно")
elif order_total >= 200:
    print("Доставка: 60 грн")
else:
    print("Мінімальне замовлення для доставки — 200 грн")

Приклад сеансу (після Позиція: — те, що ввів офіціант):

Приймаємо замовлення. Команди: кава, круасан, чай, готово
Позиція: Кава
Додано: кава — 55 грн. Разом: 55 грн
Позиція:   чай
Додано: чай — 40 грн. Разом: 95 грн
Позиція: піца
Такої позиції немає: піца
Позиція: круасан
Додано: круасан — 70 грн. Разом: 165 грн
Позиція: кава
Додано: кава — 55 грн. Разом: 220 грн
Позиція: готово
Позицій у замовленні: 4
Сума: 220 грн
Доставка: 60 грн

Що відбувається в ключових рядках:

  • рядки 3–4 — початковий стан: сума й кількість позицій дорівнюють нулю;
  • рядок 6 — цикл без умови виходу; вихід лише через break;
  • рядок 7 — введення очищається, тому "Кава" і " чай " розпізнаються;
  • рядок 10 — break на команді готово: цикл завершується, програма переходить до рядка 26;
  • рядки 12–20 — ланцюжок if / elif / else обирає ціну; для невідомої позиції спрацьовує else;
  • рядок 20 — continue не дає додати до суми позицію, якої немає в меню;
  • рядки 22–23 — зміна стану: сума і кількість позицій ростуть лише для справжніх позицій;
  • рядки 29–34 — рішення про доставку, яке ми розібрали на початку уроку.

Перевір граничний випадок: якщо одразу ввести готово, цикл завершиться на першому повторі, і програма виведе Сума: 0 грн та повідомлення про мінімальне замовлення.

Зміни приклад

Додай до програми дві зміни. Меню, ціни й решту поведінки залиш як є.

  1. Команда рахунок показує поточний стан і не завершує приймання: Зараз у замовленні: 1 поз., 55 грн.
  2. Команда готово не спрацьовує, поки замовлення порожнє: програма виводить Замовлення порожнє — додай хоча б одну позицію і чекає далі.

Приклад 1. Введення: готово, кава, рахунок, чай, готово.

Позиція: готово
Замовлення порожнє — додай хоча б одну позицію
Позиція: кава
Додано: кава — 55 грн. Разом: 55 грн
Позиція: рахунок
Зараз у замовленні: 1 поз., 55 грн
Позиція: чай
Додано: чай — 40 грн. Разом: 95 грн
Позиція: готово
Позицій у замовленні: 2
Сума: 95 грн
Мінімальне замовлення для доставки — 200 грн

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

Критерії перевірки:

  • рахунок не змінює суму і кількість позицій;
  • порожнє замовлення не завершується командою готово;
  • непорожнє замовлення завершується з першої спроби;
  • обидві команди розпізнаються незалежно від регістру й пробілів.
Підказка

Обидві нові перевірки — це ще дві гілки перед вибором ціни. Подумай, де потрібен continue, а де break, і яка змінна вже знає, чи порожнє замовлення.

Задачі з аргументами командного рядка

Кожна програма запускається з терміналу, отримує один аргумент і перевіряє його, як grade.py: неправильна кількість або не число — підказка Використання: ….

1. Зворотний відлік — countdown.py, цикл while:

$ python countdown.py 3
3
2
1
Поїхали!

2. Сума цифр — digits.py. Остання цифра числа — number % 10, відкинути її — number //= 10; повторювати, доки число не стане нулем:

$ python digits.py 2026
Сума цифр: 10
$ python digits.py 0
Сума цифр: 0
Підказка до суми цифр

Для 2026: 2026 % 10 = 6, лишається 202; 202 % 10 = 2, лишається 20; 0, лишається 2; 2, лишається 0 — стоп. 6 + 2 + 0 + 2 = 10.

Розв'язки обох — у ноутбуці заняття.

Спробуй самостійно: «Вгадай число»

Інший контекст, ті самі інструменти. Програма загадує випадкове ціле число від 1 до 20 (random.randint), а гравець вгадує його щонайбільше за 5 спроб.

Вимоги:

  • введення очищається через .strip();
  • якщо введено не ціле число або число поза межами 1–20, програма пише Потрібне ціле число від 1 до 20, і спроба не рахується;
  • на кожну помилкову спробу програма підказує Більше або Менше;
  • у разі вгадування програма пише Вгадав за N спроб(и)! і завершується;
  • після 5 невдалих спроб: Спроби закінчились. Було загадано: X.

Для перевірки тимчасово заміни випадкове число фіксованим: secret = 13.

Приклад сеансу з secret = 13:

Твоє число: 10
Більше
Твоє число: п'ять
Потрібне ціле число від 1 до 20
Твоє число: 15
Менше
Твоє число: 13
Вгадав за 3 спроб(и)!

Критерії перевірки:

  • 13 з першої спроби → Вгадав за 1 спроб(и)!;
  • 1, 2, 25, 3, 4, 5 → п'ять підказок Більше, повідомлення про межі для 25, потім Спроби закінчились. Було загадано: 13;
  • п'ять, -3 і порожній рядок не зменшують кількість спроб;
  • програма не падає з ValueError на жодному введенні.
Підказка

Тобі знадобляться лічильник спроб, умова циклу на основі цього лічильника і змінна-прапорець guessed = False, яку ти зміниш на True перед break. Після циклу за нею можна вирішити, яке повідомлення вивести.

Розширення: спроби з аргументу і while … else. Зроби версію guess.py, де кількість спроб передається при запуску (python guess.py 3) і перевіряється, як у grade.py. Замість прапорця guessed використай while … else: break при вгадуванні, а гілка else друкує відповідь, коли спроби скінчились.

$ python guess.py 2
Твоє число: 1
Більше
Твоє число: 2
Більше
Спроби закінчились. Було загадано: 13

Розв'язок — у ноутбуці заняття.

Підсумок

Що потрібно Як записати
порівняти значення ==, !=, <, <=, >, >=
перевірити діапазон 200 <= order_total < 500
обрати одну дію з кількох if / elif / else — виконується перша істинна гілка
обрати дію за конкретним значенням match + case "значення":, «усе інше» — case _: (Python 3.10+)
поєднати умови and, or, not; дужки для ясності
перевірити, що значення немає value is None
парне число number % 2 == 0
перевірити, що рядок непорожній if text:
отримати дані input() або sys.argv[1] → завжди str; .strip().lower(), .isdigit(), int()
повторювати, поки умова істинна while умова: + зміна стану в блоці
вийти з циклу break
перейти до наступної перевірки continue (стан має змінитися до нього)
дія, коли цикл завершився без break while … else

Самоперевірка

  1. Що виведе print(10 == 10.0, "10" == 10)?
  2. Чому для правила доставки не можна починати з умови order_total >= 200?
  3. Що дає bool("0") і чому?
  4. Чим if tip is None: відрізняється від if not tip:?
  5. Скільки разів виконається блок while count < 0:, якщо count = 0?
  6. Що робить continue у циклі while і чим він небезпечний?
  7. Що виведуть print(1 < 3 > 2) і print((1 < 3) > 2)?
  8. Як Python розставить дужки у виразі not guests > 10 and is_weekend?
  9. Що лежить у sys.argv після запуску python grade.py 87 і якого типу sys.argv[1]?
Відповіді
  1. True False. Числа 10 і 10.0 рівні за значенням, а рядок і число — ні.
  2. Будь-яка сума від 500 теж більша за 200, тому перша гілка перехопить її, і безкоштовна доставка стане недосяжною. Починай з найбільшої межі.
  3. True: рядок "0" непорожній, а Python не аналізує його зміст.
  4. is None спрацює лише для відсутнього значення. not tip спрацює ще й для 0 та "", тобто сплутає «не вказано» з «вказано нуль».
  5. Жодного: 0 < 0 хибна вже на першій перевірці.
  6. Пропускає решту поточного повтору і повертає до перевірки умови. Якщо зміна стану стоїть після continue, вона не виконається, і цикл може стати нескінченним.
  7. True і False. Без дужок — ланцюжок 1 < 3 and 3 > 2; з дужками — True > 2, тобто 1 > 2.
  8. (not (guests > 10)) and is_weekend: not сильніший за and, а порівняння — сильніше за not.
  9. ['grade.py', '87']; sys.argv[1] — рядок '87', тому перед обчисленнями потрібні .isdigit() і int().

Що далі

Документація