11. Повторение действий с циклами while
Программам часто нужно повторять действия много раз. Вы уже видели, как принимать решения с помощью операторов if в главе 8, но что делать, если нужно выполнять действие повторно, пока не будет выполнено определённое условие? Здесь и пригодятся циклы.
Python предоставляет два основных типа циклов: циклы while и циклы for. В этой главе мы сосредоточимся на циклах while, которые повторяют блок кода до тех пор, пока условие остаётся истинным. Цикл for, который лучше подходит для перебора последовательностей, будет рассмотрен в главе 12.
Понимание циклов while — фундамент для написания программ, которые могут многократно обрабатывать данные, проверять ввод пользователя, реализовывать игровые циклы и решать множество других реальных задач программирования.
11.1) Структура цикла while
Цикл while многократно выполняет блок кода до тех пор, пока заданное условие оценивается как True. Как только условие становится False, цикл останавливается, и программа продолжает выполнение с кода после цикла.
Базовый синтаксис цикла while
Структура цикла while выглядит так:
while condition:
# Повторяемый блок кода
# Этот код выполняется, пока condition равно Truecondition — это любое выражение, которое вычисляется в булево значение (или может интерпретироваться как истинное или ложное, как мы узнали в главе 7). Блок кода с отступом под оператором while называется телом цикла(loop body) и выполняется повторно, пока условие остаётся True.
Рассмотрим простой пример:
# Считаем от 1 до 5
count = 1
while count <= 5:
print(f"Count is: {count}")
count = count + 1 # Увеличиваем count
print("Loop finished!")Output:
Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5
Loop finished!Как это работает шаг за шагом:
- Мы инициализируем
countзначением1 - Проверяется условие
count <= 5. Поскольку1 <= 5— этоTrue, выполняется тело цикла - Внутри цикла мы выводим текущее значение счётчика, а затем увеличиваем его на 1
- После завершения тела цикла Python возвращается к оператору
whileи снова проверяет условие - Этот процесс повторяется, пока
countне станет равным6, после чего6 <= 5становитсяFalse, и цикл завершается - Программа продолжает выполнение с кода после цикла
Ключевой момент в том, что условие проверяется перед каждой итерацией (каждым повторением тела цикла). Если условие изначально равно False, тело цикла вообще ни разу не выполнится:
count = 10
while count <= 5:
print("This will never print")
print("Loop skipped entirely")Output:
Loop skipped entirelyПоскольку 10 <= 5 — это False с самого начала, тело цикла ни разу не выполняется.
Важность изменения переменной цикла
Чтобы цикл while в итоге остановился, что-то внутри цикла должно изменить условие с True на False. Обычно это означает изменение переменной (или переменных), используемой в условии. Если вы забудете это сделать, вы создадите бесконечный цикл(infinite loop) (который мы подробно обсудим в следующем разделе).
Вот пример, показывающий, почему важно обновлять переменную цикла:
# Вычисляем сумму чисел от 1 до 10
total = 0
number = 1
while number <= 10:
total = total + number # Добавляем текущее число к total
number = number + 1 # Переходим к следующему числу
print(f"The sum of numbers from 1 to 10 is: {total}")Output:
The sum of numbers from 1 to 10 is: 55В этом примере мы накапливаем сумму, проходя по числам. И total, и number изменяются на каждой итерации, но именно изменение number гарантирует, что цикл в итоге завершится, когда number станет равным 11.
Циклы while с вводом пользователя
Одно из практических применений циклов while — обработка ввода пользователя до тех пор, пока не будет выполнено конкретное условие. Давайте создадим простую игру в угадывание числа:
# Простая игра в угадывание числа
secret_number = 7
guess = 0
while guess != secret_number:
guess = int(input("Guess the number (1-10): "))
if guess < secret_number:
print("Too low! Try again.")
elif guess > secret_number:
print("Too high! Try again.")
else:
print("Correct! You guessed it!")Этот цикл продолжает запрашивать догадки до тех пор, пока пользователь не введёт правильное число. Каждая итерация обрабатывает одну попытку и выдаёт обратную связь. Цикл естественным образом завершается, когда guess становится равным secret_number, делая условие guess != secret_number равным False.
Циклы while с несколькими условиями
Вы можете использовать булевы операторы (and, or, not), чтобы создавать более сложные условия цикла, как мы узнали в главе 9:
# Обрабатываем ввод, пока пользователь не введёт "quit" или не сделает 5 попыток
attempts = 0
user_input = ""
while user_input != "quit" and attempts < 5:
user_input = input("Enter a command (or 'quit' to exit): ")
attempts += 1
if user_input == "quit":
print("Goodbye!")
else:
print(f"You entered: {user_input}")
print(f"Attempts remaining: {5 - attempts}")
if attempts >= 5 and user_input != "quit":
print("Maximum attempts reached.")Этот цикл продолжается, пока истинны оба условия: пользователь не ввёл "quit" и не превысил 5 попыток. Цикл завершается, когда любое из условий становится ложным.
Визуализация выполнения цикла while
Вот блок-схема, показывающая, как выполняется цикл while:
Цикл создаёт цикл повторения, в котором условие проверяется, тело выполняется, если условие истинно, переменные обновляются, а затем условие проверяется снова. Этот цикл продолжается, пока условие не станет ложным.
11.2) Бесконечные циклы и как их избегать
Бесконечный цикл(infinite loop) — это цикл, который никогда не завершается, потому что его условие никогда не становится False. Бесконечные циклы — одна из самых распространённых ошибок, которые новички допускают при работе с циклами while, и они могут заставить вашу программу зависнуть на неопределённое время.
Что вызывает бесконечные циклы?
Самая распространённая причина бесконечных циклов — забыть изменить переменную (или переменные), которые влияют на условие цикла. Рассмотрим проблемный пример:
# ВНИМАНИЕ: бесконечный цикл — только для демонстрации
# ПРОБЛЕМА: count никогда не изменяется
count = 1
while count <= 5:
print(f"Count is: {count}")
# Missing: count += 1Если бы вы запустили этот код, он бы печатал "Count is: 1" бесконечно, потому что count остаётся равным 1, а 1 <= 5 всегда True. Условие никогда не меняется.
Как распознать, что это бесконечный цикл: посмотрите на условие цикла (count <= 5), а затем проверьте, изменяет ли что-то внутри тела цикла count. Если нет, и условие начинается как True, у вас бесконечный цикл.
Вот исправленная версия:
# Исправленная версия с правильным увеличением
count = 1
while count <= 5:
print(f"Count is: {count}")
count += 1 # Это гарантирует, что цикл в итоге завершитсяOutput:
Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5Отладка бесконечных циклов с ограничителем безопасности
При разработке кода с циклами полезно добавлять ограничитель безопасности, чтобы предотвратить случайные бесконечные циклы во время тестирования:
# Ограничитель безопасности во время разработки
count = 1
iterations = 0
max_iterations = 100 # Ограничитель безопасности
while count <= 5 and iterations < max_iterations:
print(f"Count is: {count}")
count += 1
iterations += 1
if iterations >= max_iterations:
print("WARNING: Maximum iterations reached. Check for infinite loop.")Этот шаблон добавляет счётчик, который отслеживает, сколько раз выполнялся цикл. Если он достигает ограничителя безопасности, вы понимаете, что с логикой цикла что-то не так. Когда вы уверены, что цикл работает правильно, ограничитель безопасности можно убрать.
Бесконечные циклы с вводом пользователя
Другой распространённый сценарий для бесконечных циклов связан с проверкой корректности пользовательского ввода:
# ВНИМАНИЕ: потенциальный бесконечный цикл — только для демонстрации
# ПРОБЛЕМА: если пользователь никогда не введёт корректный ввод, цикл не закончится
age = -1
while age < 0:
age = int(input("Enter your age: "))
# Если пользователь вводит отрицательное число, цикл продолжаетсяЭтот цикл работает корректно, если пользователь в итоге вводит неотрицательное число, но он становится бесконечным циклом, если пользователь продолжает вводить отрицательные значения. Хотя это может быть приемлемо для некоторых программ (вы хотите продолжать спрашивать, пока не будет получен корректный ввод), важно понимать, что завершение цикла полностью зависит от поведения пользователя.
Более надёжный подход может включать способ выйти:
# Более удачный подход с опцией выхода
age = -1
while age < 0:
user_input = input("Enter your age (or 'quit' to exit): ")
if user_input.lower() == 'quit':
print("Exiting program.")
age = 0 # Устанавливаем корректное значение, чтобы выйти из цикла
else:
age = int(user_input)
if age < 0:
print("Age must be non-negative. Please try again.")
if age > 0:
print(f"Your age is: {age}")Бесконечные циклы из-за логических ошибок
Иногда бесконечные циклы возникают из-за логических ошибок в том, как вы обновляете переменные:
# ВНИМАНИЕ: бесконечный цикл — только для демонстрации
# ПРОБЛЕМА: count уменьшается вместо увеличения
count = 1
while count <= 5:
print(f"Count is: {count}")
count -= 1 # ОШИБКА: это уменьшает count, а не увеличиваетЭто создаёт бесконечный цикл, потому что count начинается с 1 и становится 0, -1, -2 и т. д. Поскольку отрицательные числа всегда меньше или равны 5, условие count <= 5 остаётся True навсегда.
Исправленная версия:
count = 1
while count <= 5:
print(f"Count is: {count}")
count += 1 # Правильно: увеличиваем, чтобы в итоге превысить 5Отладка бесконечных циклов
Если вы случайно создали бесконечный цикл при запуске Python-скрипта, вы можете остановить его, нажав Ctrl+C (или Cmd+C на Mac) в терминале. Это отправляет в Python сигнал прерывания с клавиатуры, который останавливает программу.
Как избегать бесконечных циклов:
- Всегда убеждайтесь, что условие цикла может стать False: проверьте, что переменные в условии изменяются внутри цикла
- Используйте правильный оператор сравнения: убедитесь, что
<=,<,!=и т. д. соответствуют вашему намерению - Сначала тестируйте с малыми значениями: прежде чем запускать цикл, который может выполниться много раз, протестируйте с небольшими пределами
- Добавляйте отладочные print-выражения: временно выводите переменную цикла, чтобы видеть, как она меняется во времени.
- Используйте ограничители безопасности при разработке: как показано ранее, добавьте счётчик максимального числа итераций во время тестирования
11.3) Использование break и continue в циклах while
Python предоставляет два специальных оператора, которые дают вам больше контроля над выполнением цикла: break и continue. Эти операторы позволяют изменять обычный ход выполнения цикла в зависимости от условий, возникающих во время выполнения.
11.3.1) Оператор break
Оператор break немедленно завершает цикл, независимо от условия цикла. Когда Python встречает break, он полностью выходит из цикла и продолжает выполнение с кода после цикла.
Вот простой пример:
# Выходим из цикла, когда достигнуто определённое значение
count = 1
while count <= 10:
if count == 5:
print("Reached 5, stopping loop")
break
print(f"Count: {count}")
count += 1
print("Loop exited")Output:
Count: 1
Count: 2
Count: 3
Count: 4
Reached 5, stopping loop
Loop exitedОбратите внимание: как только count становится равным 5, выполняется оператор break, и цикл завершается сразу же. Цикл никогда не дойдёт до count = 6, хотя условие count <= 10 по-прежнему было бы True.
Как break меняет ход выполнения цикла
Понимание того, как break меняет обычное выполнение цикла, критически важно. Вот блок-схема, показывающая различие:
Ключевой момент: break даёт немедленный путь выхода из цикла, обходя и оставшийся код тела цикла, и проверку условия.
Практическое применение break: проверка ввода
Одно из самых распространённых применений break — выйти из цикла, когда получен корректный ввод:
# Продолжаем спрашивать корректный ввод, пока не получим его
while True:
age_input = input("Enter your age (must be positive): ")
# Пытаемся преобразовать в целое число
try:
age = int(age_input)
# Проверяем, корректно ли
if age > 0:
print(f"Thank you! Your age is {age}")
break # Выходим из цикла при корректном вводе
else:
print("Age must be positive. Please try again.")
except ValueError:
print("That's not a valid number. Please try again.")
print("Input validation complete")Этот шаблон использует while True:, чтобы создать намеренный бесконечный цикл, а затем использует break для выхода, когда получен корректный ввод. Это чище, чем попытка управлять сложным условием цикла. (Примечание: здесь мы используем try и except, о которых подробно узнаем в части VII. Пока просто поймите, что это перехватывает ошибки при преобразовании ввода в целое число.)
break с несколькими условиями
Вы можете использовать break со сложными условиями, чтобы выйти из цикла, когда выполнен любой из нескольких критериев:
# Ищем конкретный элемент во вводе пользователя
search_term = "python"
attempts = 0
max_attempts = 5
while attempts < max_attempts:
user_input = input("Enter a word (or 'quit' to exit): ").lower()
attempts += 1
if user_input == 'quit':
print("User requested exit")
break
if user_input == search_term:
print(f"Found '{search_term}'!")
break
print(f"'{user_input}' is not '{search_term}'. Try again.")
print(f"Attempts remaining: {max_attempts - attempts}")
if attempts >= max_attempts:
print("Maximum attempts reached")Этот цикл может завершиться тремя способами:
- Пользователь вводит "quit" (первый
break) - Пользователь вводит искомое слово (второй
break) - Достигнуто максимальное число попыток (условие цикла становится
False)
11.3.2) Оператор continue
Оператор continue пропускает оставшуюся часть текущей итерации и переходит обратно к проверке условия цикла. В отличие от break, который полностью выходит из цикла, continue лишь переходит к следующей итерации.
Вот базовый пример:
# Печатаем только нечётные числа от 1 до 10
count = 0
while count < 10:
count += 1
if count % 2 == 0: # Если число чётное
continue # Пропускаем остальное и переходим к следующей итерации
print(f"Odd number: {count}")Output:
Odd number: 1
Odd number: 3
Odd number: 5
Odd number: 7
Odd number: 9Как это работает:
countувеличивается в начале каждой итерации- Если
countчётное (count % 2 == 0), выполняетсяcontinue - Оператор
continueпропускает операторprintи возвращает выполнение к условиюwhile - Если
countнечётное,continueне выполняется, и срабатываетprint
Как continue меняет ход выполнения цикла
Вот блок-схема, показывающая, как continue влияет на выполнение цикла:
Критическое различие между break и continue:
- break: полностью выходит из цикла, переходя к коду после цикла
- continue: пропускает оставшийся код текущей итерации и возвращается к проверке условия
Важное замечание о размещении continue
Заметьте, что count += 1 стоит до continue. Если бы мы поставили его после, чётные числа приводили бы к тому, что continue пропускал бы инкремент, создавая бесконечный цикл:
# ВНИМАНИЕ: бесконечный цикл — только для демонстрации
# ПРОБЛЕМА: continue пропускает увеличение для чётных чисел
count = 0
while count < 10:
if count % 2 == 0:
continue # Пропускает всё ниже, включая count += 1
count += 1 # MISTAKE: это никогда не выполнится для чётных чисел
print(f"Odd number: {count}")Когда count равен 0 (чётное), выполняется continue, пропуская count += 1. Затем цикл снова проверяет 0 < 10, и цикл повторяется бесконечно.
Правило: всегда убеждайтесь, что переменные цикла, влияющие на условие, обновляются до любого оператора continue, который может пропустить остальную часть тела цикла.
Практическое применение continue: фильтрация данных
Оператор continue полезен при обработке данных, когда вы хотите пропускать некоторые элементы:
# Обрабатываем только корректные оценки
score_count = 0
total_score = 0
attempts = 0
while attempts < 5:
score_input = input(f"Enter score {attempts + 1} (or 'skip' to skip): ")
attempts += 1
if score_input.lower() == 'skip':
print("Skipping this score")
continue # Переходим к следующей итерации
try:
score = int(score_input)
if score < 0 or score > 100:
print("Score must be between 0 and 100. Skipping.")
continue # Пропускаем некорректные оценки
# Корректная оценка — обрабатываем её
total_score += score
score_count += 1
print(f"Score recorded: {score}")
except ValueError:
print("Invalid input. Skipping.")
continue
if score_count > 0:
average = total_score / score_count
print(f"\nAverage of {score_count} valid scores: {average:.1f}")
else:
print("\nNo valid scores entered")Этот пример демонстрирует несколько применений continue:
- пропуск, когда пользователь вводит "skip"
- пропуск, когда оценка вне допустимого диапазона
- пропуск, когда ввод не является корректным числом
Каждый continue предотвращает добавление оценки к сумме, но цикл продолжается со следующей попытки.
Совместное использование break и continue
Вы можете использовать и break, и continue в одном цикле для гибкого управления:
# Обрабатываем числа, пока сумма не превысит 100, пропуская отрицательные числа
total = 0
count = 0
while True:
number_input = input("Enter a number (or 'done' to finish): ")
if number_input.lower() == 'done':
print("User finished entering numbers")
break # Выходим из цикла
try:
number = int(number_input)
if number < 0:
print("Negative numbers not allowed. Skipping.")
continue # Переходим к следующей итерации
total += number
count += 1
print(f"Added {number}. Current total: {total}")
if total > 100:
print("Total exceeded 100. Stopping.")
break # Выходим из цикла при достижении лимита
except ValueError:
print("Invalid input. Skipping.")
continue
print(f"\nFinal total: {total} (from {count} numbers)")Этот цикл демонстрирует:
breakдля выхода, когда пользователь вводит "done"continueдля пропуска отрицательных чиселcontinueдля пропуска некорректного вводаbreakдля выхода, когда сумма превышает 100
11.4) Использование else с циклами while
В Python есть уникальная возможность, которой нет во многих других языках программирования: к циклу while можно прикрепить блок else. Этот блок else выполняется только если цикл завершился нормально (то есть условие цикла стало False без выполнения оператора break).
Базовый синтаксис else с while
Синтаксис выглядит так:
while condition:
# Тело цикла
else:
# Это выполняется только если цикл завершился нормально
# (не был прерван break)Рассмотрим простой пример:
# Считаем от 1 до 5 с блоком else
count = 1
while count <= 5:
print(f"Count: {count}")
count += 1
else:
print("Loop completed normally")
print("Program continues")Output:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Loop completed normally
Program continuesБлок else выполняется, потому что цикл работал до тех пор, пока его условие (count <= 5) не стало False. Цикл завершился «нормально», без прерывания.
Когда else НЕ выполняется: оператор break
Ключевое поведение else в цикле — он не выполняется, если цикл завершился оператором break:
# Поиск числа с break
count = 1
target = 3
while count <= 5:
print(f"Checking: {count}")
if count == target:
print(f"Found {target}!")
break
count += 1
else:
print("Target not found in range")
print("Search complete")Output:
Checking: 1
Checking: 2
Checking: 3
Found 3!
Search completeОбратите внимание, что блок else ("Target not found in range") не выполнился, потому что цикл был завершён оператором break. Это ключевое различие: else выполняется только когда цикл завершился нормально (условие стало False), а не когда выход произошёл через break.
Теперь посмотрим, что произойдёт, если цель не найдена:
# Поиск числа, которого не существует
count = 1
target = 7 # Не входит в диапазон 1-5
while count <= 5:
print(f"Checking: {count}")
if count == target:
print(f"Found {target}!")
break
count += 1
else:
print("Target not found in range")
print("Search complete")Output:
Checking: 1
Checking: 2
Checking: 3
Checking: 4
Checking: 5
Target not found in range
Search completeНа этот раз цикл завершил все итерации, не найдя цель, поэтому условие count <= 5 в итоге стало False, и блок else выполнился.
Как else работает при завершении цикла
Вот блок-схема, показывающая пути выполнения с блоком else:
Блок else достигается только тогда, когда условие цикла естественным образом становится False. Если встречается break, поток выполнения перескакивает прямо мимо блока else к коду после цикла.
Практическое применение: операции поиска
Блок else особенно полезен для операций поиска, когда вы хотите узнать, было ли что-то найдено:
# Ищем корректный пароль в списке попыток
valid_password = "python123"
max_attempts = 3
attempts = 0
while attempts < max_attempts:
password = input(f"Enter password (attempt {attempts + 1}/{max_attempts}): ")
attempts += 1
if password == valid_password:
print("Access granted!")
break
else:
print("Access denied. Maximum attempts exceeded.")
print("Account locked.")Если пользователь вводит правильный пароль, выполняется break, и блок else пропускается. Если все попытки использованы без успеха, цикл завершается нормально, и выполняется блок else, указывая на неудачу.
else с continue
Оператор continue не мешает выполнению блока else. Это делает только break:
# continue не влияет на выполнение else
count = 0
while count < 5:
count += 1
if count == 3:
print(f"Skipping {count}")
continue # Переходим к следующей итерации
print(f"Processing {count}")
else:
print("Loop completed normally (continue doesn't prevent this)")Output:
Processing 1
Processing 2
Skipping 3
Processing 4
Processing 5
Loop completed normally (continue doesn't prevent this)Блок else выполнился, потому что цикл завершился нормально. Оператор continue влияет только на отдельные итерации, а не на общее завершение цикла.
Сравнение else с break и без break
Посмотрим сравнение бок о бок:
# Пример 1: Находим первое число, кратное 7 (с break)
print("Finding first number divisible by 7:")
number = 1
while number <= 20:
if number % 7 == 0:
print(f"Found: {number}")
break
number += 1
else:
print("No number divisible by 7 found in range")
print()
# Пример 2: Проверяем все числа (без break)
print("Checking all numbers for divisibility by 7:")
number = 1
while number <= 20:
if number % 7 == 0:
print(f"Found: {number}")
number += 1
else:
print("Finished checking all numbers")Output:
Finding first number divisible by 7:
Found: 7
Checking all numbers for divisibility by 7:
Found: 7
Found: 14
Finished checking all numbersВ первом примере break останавливает цикл после нахождения первого совпадения, поэтому else не выполняется. Во втором примере цикл проверяет все числа и завершается нормально, поэтому else выполняется.
Когда использовать else с циклами while
Блок else наиболее полезен, когда:
- Операции поиска: вы хотите знать, было ли что-то найдено
- Проверка с ограниченным числом попыток: нужно обработать случай, когда все попытки исчерпаны
- Обработка с ранним выходом: нужно разное поведение для «всё завершили» и «остановились раньше»
Однако else с циклами может быть запутанным для программистов, пришедших из других языков (где такой возможности нет). Иногда понятнее использовать флаг-переменную:
# Использование блока else
attempts = 0
while attempts < 3:
password = input("Enter password: ")
attempts += 1
if password == "secret":
print("Access granted")
break
else:
print("Access denied")
# Эквивалент с использованием флаг-переменной (иногда понятнее)
attempts = 0
access_granted = False
while attempts < 3:
password = input("Enter password: ")
attempts += 1
if password == "secret":
print("Access granted")
access_granted = True
break
if not access_granted:
print("Access denied")Оба подхода работают. Выбирайте тот, который делает ваш код более понятным для вашей конкретной ситуации.
Понимание else в циклах while даёт вам ещё один инструмент для написания ясного, выразительного кода, особенно в сценариях поиска и проверки, где нужно различать «найдено» и «не найдено» или «успех» и «неудача после всех попыток».
В этой главе мы подробно рассмотрели циклы while, узнав, как:
- Строить циклы, которые повторяют код на основе условий
- Избегать бесконечных циклов, правильно обновляя переменные цикла
- Использовать
break, чтобы при необходимости завершать цикл раньше - Использовать
continue, чтобы пропускать итерации на основе условий - Использовать
else, чтобы обрабатывать случаи, когда цикл завершается нормально безbreak
Эти инструменты дают вам мощный контроль над повторением в ваших программах. В следующей главе мы изучим циклы for, которые дают более удобный способ перебирать последовательности, такие как строки, списки и диапазоны.