Глава 10 · Немного автоматизации!

Отладка циклов

Десять типовых ошибок циклов и универсальный метод их поиска — таблица трассировки, построенная вручную, строка за строкой.

Как читать чужой (и свой) сломанный цикл

Циклы — самое частое место, где программа работает, но выдаёт не тот результат, или вообще зависает. Хорошая новость: подавляющее большинство таких ошибок укладывается всего в десяток типовых сценариев. Разберём их по одному.

Важно: мы НЕ будем запускать зависающий код
Некоторые примеры ниже описывают бесконечные циклы — в практике этой страницы вы будете читать и предсказывать поведение кода, а не запускать реальный зависающий цикл в браузере. Если код в вашей программе действительно завис — остановите его вручную (Ctrl+C в терминале, «Interrupt kernel» в Jupyter), не дожидаясь самостоятельной остановки.

10 типичных ошибок циклов

1. Забыли обновить состояние
bug.py
while count < 5:
    print(count)
    # забыли count += 1

Условие никогда не станет ложным — бесконечный цикл. Проверьте, что внутри while есть строка, меняющая переменную из условия.

2. Ошибка на единицу в range()
bug.py
for i in range(1, 10):
    print(i)  # не выведет 10!

range(1, 10) даёт 1..9 — если нужно включить 10, это range(1, 11). Классическая off-by-one ошибка.

3. Неверный отступ тела
bug.py
for n in chisla:
print(n)  # SyntaxError: ожидался отступ

Тело цикла обязано иметь отступ. Без него Python даже не запустит программу.

4. Счётчик обнулён внутри цикла
bug.py
for n in chisla:
    kolichestvo = 0
    kolichestvo += 1

kolichestvo сбрасывается на каждой итерации — в конце в нём всегда 1, а не общее количество. Инициализация должна быть ДО цикла, не внутри.

5. break на неверном уровне отступа
bug.py
for n in chisla:
    if n == 5:
    break  # не в теле if!

break, стоящий вне if (из-за отступа), сработает на первой же итерации независимо от условия. Проверяйте отступы так же внимательно, как и условия.

6. continue перед обновлением
bug.py
while count < 5:
    if count == 2:
        continue
    print(count)
    count += 1

Когда count == 2, continue пропускает count += 1 — и count навсегда останется равным 2. Бесконечный цикл, и его особенно трудно заметить.

7. Путаница переменных вложенных циклов
bug.py
for i in range(3):
    for i in range(4):
        print(i)

Внутренний цикл использует то же имя i, что и внешний — внешнее значение i затирается и теряется. Используйте разные имена, например i и j.

8. Неверный отрицательный шаг
bug.py
for n in range(10, 0):
    print(n)  # ничего не выведет

range(10, 0) с шагом по умолчанию +1 никогда не дойдёт от 10 до 0 — нужен range(10, 0, -1).

9. Несоответствие состояния и условия
bug.py
while spisok:
    print(spisok[0])
    # забыли удалить элемент из spisok

Условие проверяет spisok (пока список не пуст), но тело никогда не уменьшает список — бесконечный цикл, хотя выглядит иначе, чем счётчик.

10. Условие ложно с самого начала
bug.py
n = 10
while n < 5:
    print(n)
    n += 1

Тело не выполнится ни разу — ноль итераций. Это не ошибка сама по себе, но частая неожиданность, если условие не проверили заранее.

Метод отладки: таблица трассировки вручную

Когда цикл ведёт себя не так, как ожидалось, самый надёжный способ разобраться — построить ту же таблицу трассировки, что мы использовали весь этот раздел, но теперь для реального, сломанного кода:

ИтерацияУсловиеПеременные ДО телаЧто выводитсяПеременные ПОСЛЕ тела
1
2
Заполняйте таблицу построчно, как будто вы — Python
Не пытайтесь угадать результат целиком. Возьмите первую итерацию, честно распишите условие, значения переменных до и после тела — и повторяйте, пока не увидите, где ожидание разошлось с тем, что происходит на самом деле.

Off-by-one — отдельный разбор

«Ошибка на единицу» — самая частая семья багов циклов: цикл выполняется на один раз больше или меньше, чем нужно.

12345678910stop — исключён
range(1, 10) — последнее значение 9, не 10!
1234567891011stop — исключён
range(1, 11) — правильно, если нужно дойти до 10 включительно

Чек-лист граничных случаев

ВопросЗачем проверять
Что произойдёт при нуле повторов (пустая последовательность)?тело цикла не выполнится вообще — не сломается ли код после цикла
Что произойдёт при одном повторе?самый частый источник ошибок на единицу
Включена ли последняя граница туда, куда нужно?stop у range() и срезов не включён по умолчанию
Совпадает ли направление шага с направлением start→stop?положительный шаг для роста, отрицательный — для убывания
СлучайПримерСколько итераций
Нольfor n in []:0 — тело не выполнится ни разу
Одинfor n in [7]:1
Многоfor n in range(1000):1000

Переменная цикла после его завершения

peremennaya_posle_cikla.py
for n in range(5):
    pass

print(n)  # 4 — последнее значение, которое приняла n
Переменная не исчезает после for
После завершения for переменная цикла остаётся связана с последним полученным значением — в отличие от некоторых других языков, в Python у циклов нет отдельной «области видимости». Исключение: если последовательность была пустой, тело не выполнилось ни разу, и переменная цикла вообще не была создана — обращение к ней после цикла в этом случае вызовет NameError.

Имена переменных цикла

Короткие имена вроде i, j, k — давняя традиция для простых числовых счётчиков (особенно во вложенных циклах), но как только переменная цикла имеет реальный смысл — называйте её осмысленно: for slovo in slova: читается понятнее, чем for x in y:.

Затенение переменных (shadowing)
Если внутри цикла переменной цикла присвоить новое значение вручную, или использовать имя, которое уже существовало снаружи цикла, — можно случайно затереть нужное значение. Особенно легко так ошибиться во вложенных циклах с одинаковыми именами (см. ошибку №7 выше).

Предпросмотр: изменение списка во время перебора

Не изменяйте список, который сейчас перебираете
Удаление или добавление элементов в список прямо во время цикла for элемент in spisok: может привести к тому, что некоторые элементы будут пропущены или обработаны дважды. Подробно разберём эту тему и безопасные способы её обхода в главе про списки — сейчас достаточно знать, что так делать не стоит.

Немного о производительности

Каждая лишняя вложенность цикла умножает число итераций (мы уже видели это на примере таблицы умножения). Пока считать нужно тысячи, а не миллионы значений — разница незаметна. Формальный разбор скорости алгоритмов ждёт вас в старших главах — сейчас достаточно интуиции: вложенный цикл внутри цикла обычно работает заметно медленнее одного цикла того же размера.

Практика: найди ошибку в цикле
Интерактивный ноутбук прямо в браузере — Python 3.14 через Pyodide, без установки
Открыть практику →
Практика: off-by-one — посчитай итерации
Интерактивный ноутбук прямо в браузере — Python 3.14 через Pyodide, без установки
Открыть практику →