Циклы for и while в Python: разбор на живых задачах
Когда брать for, а когда while, чем платит range за читаемость и почему while True с break иногда честнее сложного условия. Разбираем на задачах, которые встречаются в реальном коде.

В Python два цикла — for и while, и почти всё, что можно сделать одним, делается и другим. Вопрос не «какой умеет», а «какой честнее описывает намерение». Ниже — четыре задачи, где выбор между ними меняет читаемость, скорость и количество багов. Примеры на CPython 3.12, но всё работает начиная с 3.8.
Задача 1: обойти коллекцию
Самое частое, что делают с циклом, — проходят по списку, строке или словарю. Здесь while с ручным индексом почти всегда проигрывает.
# Так пишут те, кто пришёл из C
numbers = [10, 20, 30]
i = 0
while i < len(numbers):
print(numbers[i])
i += 1
# Так пишут на Python
for number in numbers:
print(number)
Вариант с while хранит состояние i, которое вы можете забыть увеличить (получите бесконечный цикл) или увеличить дважды. for берёт элементы напрямую через протокол итератора — переменной-счётчика просто нет, а значит нет и класса ошибок вокруг неё.
Индекс нужен реально? Тогда enumerate, а не ручной счётчик:
for index, number in enumerate(numbers):
print(index, number) # 0 10 / 1 20 / 2 30
Если в циклеforпоявился ручной счётчикi += 1— почти всегда этоenumerate, который забыли применить.
Задача 2: повторить N раз
Нужно выполнить действие ровно пять раз. Классическая ловушка новичка — range с неочевидными границами.
for attempt in range(5):
print(attempt) # 0, 1, 2, 3, 4 — пять значений, не шесть
range(5) даёт числа от 0 до 4 включительно — верхняя граница не входит. Это не каприз языка, а следствие того, что срезы и range работают по полуоткрытому интервалу: range(a, b) — это b - a чисел. Отсюда удобное свойство: range(0, 5) и range(5, 10) вместе покрывают ровно range(0, 10) без пропусков и наложений.
range — ленивый* объект: он не создаёт список из миллиона чисел в памяти, а вычисляет их по одному. Поэтому for x in range(1_000_000) не съедает память под миллион элементов.
Если счётчик в теле цикла не используется, имя принято заменять на подчёркивание — это сигнал читателю «значение не важно, важно число повторов»:
for _ in range(3):
send_ping() # три пинга подряд, номер попытки не нужен
Задача 3: цикл, у которого нет заранее известного конца
Вот здесь while — на своём месте, а for начинает мешать. Читаем строки от пользователя, пока он не введёт пустую:
lines = []
while True:
line = input("Строка (пустая — стоп): ")
if line == "":
break
lines.append(line)
print(f"Собрано строк: {len(lines)}")
Условие остановки зависит от того, что ввёл пользователь, — заранее число итераций неизвестно. Конструкция while True с явным break честнее, чем попытка засунуть проверку в условие цикла:
# Хуже: условие проверяется до того, как line существует
line = input("Строка: ")
while line != "":
lines.append(line)
line = input("Строка: ") # дублирование input в двух местах
Во втором варианте input вызывается в двух местах, и при доработке легко изменить только одно. while True оставляет одну точку чтения и одну точку выхода.
Опрос до успеха: while с попытками
Классический случай — ждём готовности внешнего ресурса, но не бесконечно:
import time
max_attempts = 5
for attempt in range(1, max_attempts + 1):
if resource_ready(): # ваша функция проверки
print("Готово")
break
time.sleep(2)
else:
# else у цикла срабатывает, только если break не сработал
print(f"Не дождались за {max_attempts} попыток")
Здесь попыток конечное число, поэтому for range уместнее бесконечного while: лимит виден прямо в заголовке цикла. Обратите внимание на else у цикла — блок выполняется, если цикл завершился без break. Это редкая и часто забываемая конструкция, но именно она избавляет от флага found = False.
Задача 4: изменять коллекцию во время обхода
Тут ломается самый безобидный на вид for. Удалять элементы из списка, по которому идёте, — прямой путь к пропущенным элементам:
values = [1, 2, 3, 4, 5, 6]
for value in values:
if value % 2 == 0:
values.remove(value) # НЕ ДЕЛАЙТЕ ТАК
print(values) # [1, 3, 5] здесь повезло, но логика хрупкая
Итератор ведёт внутренний индекс, а remove сдвигает элементы влево — часть значений проскакивает мимо. На других данных вы получите неверный результат без единой ошибки. Правильно — не менять исходник, а строить новый:
values = [1, 2, 3, 4, 5, 6]
values = [v for v in values if v % 2 != 0] # [1, 3, 5]
Если менять список нужно на месте (например, объект переиспользуется), идите по копии: for value in values[:] — срез создаёт независимую копию для обхода.
Когда что выбирать
| Ситуация | Инструмент | Почему |
|---|---|---|
| Обход списка, строки, словаря | for x in ... | Нет счётчика — нет ошибок с ним |
| Нужен индекс при обходе | for i, x in enumerate(...) | Индекс и значение вместе, без ручного += 1 |
| Повторить фиксированное число раз | for _ in range(n) | Число повторов видно в заголовке |
| Конечное число попыток с проверкой | for ... range + else | Лимит явный, else ловит «не удалось» |
| Конец неизвестен, зависит от событий | while True + break | Одна точка выхода, нет ложного лимита |
Простое правило: если можете посчитать или назвать источник элементов заранее — берите for. Если остановка зависит от того, что случится внутри цикла, — while.
Что ломается чаще всего
- Бесконечный цикл в while. Забыли изменить переменную условия или
breakнедостижим. Если запустили и программа висит — нажмите Ctrl+C, посмотрите, в какой строке она остановилась. - Off-by-one в range.
range(1, 5)— это 1, 2, 3, 4, а не 1–5. Когда нужна верхняя граница включительно, пишитеrange(1, 5 + 1). - Изменение коллекции при обходе. Молчаливо пропускает элементы. Стройте новый список или идите по срезу-копии.
- break и continue в глубоко вложенных циклах.
breakвыходит только из ближайшего цикла, а не из всех сразу. Для выхода из вложенности выносите логику в функцию сreturn.
Что делать дальше
Когда цикл сводится к «собрать новый список/множество/словарь из старого», замените его на генератор списков (list comprehension) — короче и быстрее. Для конвейеров над большими данными смотрите в сторону генераторов и yield: они не держат всё в памяти. А если по коллекции нужно пройти сразу двумя итераторами параллельно — это zip, отдельная тема, здесь мы разбирали только сами циклы.
Задачи: математика, физика, химия: Разбор задачи по шагам с проверкой и похожей задачей
Спросить за 10 ₽Источники: Python docs — The for statement / compound statements, Python docs — range()
Частые вопросы
Что быстрее — for или while?
Можно ли из вложенного цикла выйти сразу из всех?
Зачем нужен else у цикла и почему я его никогда не видел?
У меня другая версия Python — примеры сработают?
Программа зависла в бесконечном цикле, что делать?
Почему нельзя удалять элементы из списка прямо в цикле for?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.