Программирование с нуля Циклы: как заставить программу повторять действия
0%

Циклы: как заставить программу повторять действия

Циклы: как заставить программу повторять действия

Представьте, что вам нужно поздравить с праздником трёх коллег. Можно написать три строки — терпимо. А если коллег триста? А если список приходит из файла и вы заранее не знаете, сколько там имён — десять или десять тысяч?

Вот здесь и появляется цикл — конструкция, которая говорит компьютеру: «выполни вот этот кусок кода много раз». Тело цикла пишется один раз, а работает столько, сколько нужно. Это, пожалуй, первый момент в обучении, когда становится по-настоящему видно, зачем нужен компьютер: он не устаёт и не ошибается на трёхсотом повторении.

Мы будем опираться на условия и переменные. Если что-то подзабылось — ничего страшного, я буду напоминать по ходу.

Аналогия: стопка писем

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

Это ровно цикл for: есть набор элементов и есть действие, которое применяется к каждому.

Второй бытовой сюжет: вы варите соус и мешаете его, пока он не загустеет. Сколько раз мешать — заранее неизвестно, вы каждый раз смотрите на кастрюлю и решаете, продолжать или хватит. Это цикл while.

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

Цикл for: «для каждого элемента»

Начнём с самого частого цикла в Python.

names = ["Аня", "Боб", "Вера"]

for name in names:
    print("Привет,", name)

Вывод:

Привет, Аня
Привет, Боб
Привет, Вера

Разберём построчно, буквально по словам:

  1. names = ["Аня", "Боб", "Вера"] — создаём список (list). Список — это несколько значений, сложенных в одну переменную по порядку; записывается в квадратных скобках через запятую. Подробно списки разберём в статье Списки, словари и множества, пока достаточно понимать: это «стопка писем».
  2. for name in names: — читается как «для каждого name в names». Слово for начинает цикл, name — имя переменной, которую Python создаст сам, in — служебное слово, names — откуда брать элементы. В конце двоеточие, как у if.
  3. print("Привет,", name)тело цикла. Сдвинуто на четыре пробела вправо: отступ говорит Python, что эта строка принадлежит циклу и должна повторяться.

Что происходит при запуске: Python берёт первый элемент списка, кладёт его в переменную name, выполняет тело. Потом берёт второй элемент, кладёт в ту же самую name (старое значение затирается), снова выполняет тело. И так пока элементы не кончатся.

Как цикл for разворачивается в повторяющиеся проходы

Один такой проход называется итерацией (iteration, от латинского «повторение»). Фраза «на третьей итерации» просто означает «на третьем проходе цикла» — не пугайтесь, слово страшнее сути.

Вот тот же механизм в виде схемы:

Обратите внимание на стрелку от «Выполнить тело» обратно к вопросу — именно она делает цикл циклом. Программа не идёт строго сверху вниз, она возвращается назад.

Перебирать можно не только списки

Строка тоже состоит из элементов — из символов. По ней можно пройтись тем же for:

word = "код"

for letter in word:
    print(letter)

Вывод:

к
о
д

Про имя переменной: name, letter, item — вы выбираете его сами, Python не придаёт ему смысла. Называйте по смыслу содержимого: не x, а letter. Через месяц вы скажете себе спасибо.

range: когда нужно просто «повторить N раз»

Часто списка нет, а есть желание повторить действие определённое число раз. Для этого существует range — генератор последовательности целых чисел.

for i in range(5):
    print("Проход номер", i)

Вывод:

Проход номер 0
Проход номер 1
Проход номер 2
Проход номер 3
Проход номер 4

Присмотритесь: счёт начался с нуля и закончился на четвёрке. range(5) даёт пять чисел — 0, 1, 2, 3, 4. Пятёрка не входит.

Это сбивает с толку всех без исключения новичков, поэтому запомните формулировку: range(5) — это «пять чисел, начиная с нуля», а не «до пяти включительно». Верхняя граница всегда не входит.

Полная форма — range(начало, конец, шаг):

Запись Какие числа даёт
range(5) 0, 1, 2, 3, 4
range(1, 6) 1, 2, 3, 4, 5
range(0, 10, 2) 0, 2, 4, 6, 8
range(10, 0, -1) 10, 9, 8, … 1
# Таблица умножения на 7
for i in range(1, 11):
    print(7, "x", i, "=", 7 * i)

Вывод (первые строки):

7 x 1 = 7
7 x 2 = 14
7 x 3 = 21

Имя i — многолетняя традиция для счётчика цикла (от английского index). Это один из немногих случаев, когда однобуквенное имя считается нормальным и понятным.

Накопление результата: главный приём

Сам по себе print в цикле — это только полдела. Гораздо чаще нужно что-то посчитать, пройдясь по данным. Приём называется накопитель (accumulator), и устроен он всегда одинаково:

  1. До цикла создать переменную с «пустым» начальным значением.
  2. Внутри цикла изменять её на каждой итерации.
  3. После цикла — использовать результат.
prices = [120, 340, 55, 890]

total = 0                  # 1. пустой кошелёк ДО цикла

for price in prices:
    total = total + price  # 2. на каждой итерации добавляем цену

print("Итого:", total)     # 3. используем результат ПОСЛЕ цикла

Вывод:

Итого: 1405

Строка total = total + price поначалу выглядит как математическая нелепость («тотал равен тоталу плюс цена»?). Но это не уравнение, а команда, и читать её надо справа налево: «возьми текущее значение total, прибавь price, результат положи обратно в total».

Проследим по шагам:

Итерация price total до total после
1 120 0 120
2 340 120 460
3 55 460 515
4 890 515 1405

Такую запись сокращают до total += price — знак += означает «прибавь к тому, что уже есть». Есть и родственники: -=, *=, /=.

Тот же приём для подсчёта количества:

words = ["дом", "кот", "программа", "я", "компьютер"]

long_count = 0                # счётчик длинных слов

for word in words:
    if len(word) > 4:         # len(...) возвращает длину строки
        long_count += 1       # +1 только если слово длинное

print("Длинных слов:", long_count)

Вывод:

Длинных слов: 2

Здесь if стоит внутри цикла: он проверяется заново на каждой итерации. Отступы показывают вложенность — if сдвинут на 4 пробела (принадлежит циклу), long_count += 1 на 8 (принадлежит if).

Ещё один вариант накопителя — поиск максимума:

temperatures = [12, 19, 8, 25, 17]

hottest = temperatures[0]     # начинаем с первого значения, а не с нуля!

for t in temperatures:
    if t > hottest:
        hottest = t

print("Максимум:", hottest)   # выведет: Максимум: 25

Важная деталь: начальное значение — первый элемент списка, а не 0. Если бы температуры были отрицательными ([-12, -5, -30]), старт с нуля дал бы неверный ответ 0, которого в данных вообще нет. Такие мелочи и отличают работающую программу от «вроде работает».

Цикл while: «пока условие истинно»

for знает заранее, сколько будет повторений — столько, сколько элементов. Но бывает иначе: повторяем, пока что-то верно, а сколько раз получится — заранее неизвестно. Это while (читается «уайл», переводится «пока»).

password = ""                       # пустая строка — пока ничего не ввели

while password != "открой":
    password = input("Скажи пароль: ")

print("Проходи!")

Пример запуска:

Скажи пароль: привет
Скажи пароль: пусти
Скажи пароль: открой
Проходи!

Читается почти по-русски: «пока password не равен "открой" — спрашивай пароль». Как только условие стало ложным, цикл прекращается.

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

Классический while со счётчиком выглядит так:

count = 3

while count > 0:
    print(count)
    count -= 1          # ОБЯЗАТЕЛЬНО: приближаем условие к ложному

print("Поехали!")

Вывод:

3
2
1
Поехали!

Бесконечный цикл — и как из него выйти

Уберите строку count -= 1 из примера выше. Что произойдёт? count навсегда останется равным 3, условие count > 0 всегда истинно, и программа будет печатать тройку до скончания веков. Это бесконечный цикл (infinite loop) — самая известная ловушка while.

Не пугайтесь, если попадётесь: это происходит абсолютно со всеми, и ничего страшного не случается. Нажмите Ctrl+C в терминале (Ctrl+C, не Ctrl+Break и не крестик окна) — программа немедленно остановится и напишет что-то вроде KeyboardInterrupt. Это не ошибка в вашем коде, а сообщение «остановлено пользователем».

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

Иногда бесконечный цикл создают намеренно — например, в программе с меню, которая работает, пока пользователь не выберет «выход». Тогда пишут while True: и выходят изнутри с помощью break, о котором прямо сейчас.

Когда что выбирать

Ситуация Цикл
Пройтись по списку, строке, файлу for
Повторить ровно N раз for с range
Повторять, пока пользователь не введёт нужное while
Повторять, пока не выполнится условие в данных while

Практическое правило новичка: если можно for — пишите for. Он короче и в него невозможно случайно попасть навсегда.

break и continue: управление изнутри цикла

Два слова, которые вмешиваются в ход цикла:

  • break — «прервать»: немедленно выйти из цикла целиком, оставшиеся итерации не выполнять.
  • continue — «продолжить»: пропустить остаток тела и перейти к следующей итерации.

Поиск с досрочным выходом — самый частый повод для break:

numbers = [4, 9, 15, 22, 7, 30]

for n in numbers:
    if n % 2 == 0 and n > 10:      # чётное И больше десяти
        print("Нашли:", n)
        break                       # дальше искать незачем

Вывод:

Нашли: 22

Числа 7 и 30 даже не проверялись — break вышел сразу. На списке из шести чисел это неважно, а на миллионе строк лога разница между «проверить все» и «остановиться на найденном» вполне ощутима.

continue пропускает неинтересные элементы:

lines = ["привет", "", "как дела", "", "пока"]

for line in lines:
    if line == "":
        continue                    # пустую строку пропускаем
    print("Строка:", line)

Вывод:

Строка: привет
Строка: как дела
Строка: пока

Честно говоря, тот же результат даёт обычный if line != "": — и для новичка это часто понятнее. continue выигрывает, когда «отсеивающих» проверок несколько: без него тело уехало бы вправо на много отступов.

А вот меню на while True и break — классика:

while True:
    command = input("Команда (привет / выход): ")

    if command == "привет":
        print("И тебе привет!")
    elif command == "выход":
        print("До встречи")
        break                       # единственный выход из цикла
    else:
        print("Не понимаю такой команды")

while True означает «условие всегда истинно», то есть заведомо бесконечный цикл. Он безопасен ровно потому, что внутри есть break.

Вложенные циклы

Цикл может стоять внутри другого цикла. Бытовая аналогия: для каждого дня недели перебрать каждый урок в расписании.

for i in range(1, 4):          # внешний цикл: 1, 2, 3
    for j in range(1, 4):      # внутренний цикл: 1, 2, 3
        print(i, "x", j, "=", i * j)
    print("---")               # эта строка принадлежит ВНЕШНЕМУ циклу

Вывод:

1 x 1 = 1
1 x 2 = 2
1 x 3 = 3
---
2 x 1 = 2
2 x 2 = 4
2 x 3 = 6
---
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
---

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

Отсюда важное следствие: тело внутреннего цикла выполнится 3 × 3 = 9 раз. Если бы каждый диапазон был по 1000, получилось бы миллион выполнений. Вложенные циклы — обычный инструмент, но за их «стоимостью» стоит следить: время работы умножается, а не складывается.

Обратите внимание и на отступы — они здесь единственный способ понять структуру. print("---") сдвинут на 4 пробела, значит принадлежит внешнему циклу и печатается три раза, а не девять.

Типичные ошибки и что означают сообщения

1. IndentationError: expected an indented block

for i in range(3):
print(i)             # нет отступа!

Дословно: «ошибка отступа: ожидался блок с отступом». Python увидел двоеточие и приготовился читать тело цикла, а тело не сдвинуто. Лечится одним нажатием Tab (редактор превратит его в 4 пробела).

2. Пропущенное двоеточие

for i in range(3)
    print(i)

SyntaxError: expected ':' — «синтаксическая ошибка: ожидалось двоеточие». Python сообщает, чего именно ему не хватило. Двоеточие обязательно после for, while, if — везде, где дальше идёт блок.

3. TypeError: 'int' object is not iterable

for i in 5:          # так нельзя
    print(i)

Дословно: «ошибка типа: объект типа int не является перебираемым». «Перебираемый» (iterable) — тот, у кого есть элементы: список, строка, range. Число 5 — не набор элементов, а одно значение, перебирать в нём нечего. Правильно: for i in range(5):.

4. Забыли изменить переменную в while

count = 0
while count < 5:
    print(count)     # count никогда не растёт -> бесконечный цикл

Сообщения не будет — программа просто зависнет и будет печатать нули. Ctrl+C, добавить count += 1, запустить снова.

5. Накопитель объявлен внутри цикла

prices = [100, 200, 300]

for price in prices:
    total = 0            # ОШИБКА: обнуляется на каждой итерации
    total += price

print(total)             # выведет 300, а не 600

Программа не падает, просто отвечает неправильно — это логическая ошибка, самый коварный вид. total = 0 должно стоять до цикла, без отступа. Проверьте себя вопросом: «эта строка должна выполниться один раз или каждый раз?»

6. NameError после цикла

for i in range(0):       # ноль повторений — тело не выполнилось ни разу
    result = i * 2

print(result)            # NameError: name 'result' is not defined

«Ошибка имени: имя result не определено». Переменная создаётся только когда строка действительно выполнилась. Если цикл не сделал ни одной итерации, переменной не существует. Поэтому накопители и заводят заранее, до цикла.

7. Ошибка «на единицу»

for i in range(1, 10):
    print(i)             # напечатает 1..9, а не 1..10

У этого класса ошибок есть имя — off-by-one, «промах на единицу». Ничего не падает, просто результат смещён на один элемент. Если программа обработала 99 записей вместо 100 — проверяйте границы range в первую очередь.

8. Изменение списка во время перебора

numbers = [1, 2, 3, 4]

for n in numbers:
    if n % 2 == 0:
        numbers.remove(n)    # удаляем прямо во время цикла

print(numbers)               # выведет [1, 3, 4] — четвёрка уцелела

Python перебирает список по позициям, и удаление сдвигает элементы — часть просто «проскакивает». Правило: не изменяйте список, по которому идёте. Собирайте результат в новый список, а старый оставьте в покое.

Собираем всё вместе

Небольшая программа: считаем статистику по оценкам.

grades = [5, 3, 4, 5, 2, 4, 5]

total = 0          # сумма всех оценок
best_count = 0     # сколько пятёрок
worst = grades[0]  # худшая оценка

for grade in grades:
    total += grade
    if grade == 5:
        best_count += 1
    if grade < worst:
        worst = grade

average = total / len(grades)     # len(...) — количество элементов

print("Оценок всего:", len(grades))
print("Средний балл:", round(average, 2))   # round(x, 2) — округление до 2 знаков
print("Пятёрок:", best_count)
print("Худшая оценка:", worst)

Вывод:

Оценок всего: 7
Средний балл: 4.0
Пятёрок: 3
Худшая оценка: 2

Заметьте: один проход по данным — и сразу три накопителя. Это типично и правильно: незачем ходить по списку трижды, если всё считается за один раз.

Практика

Задания идут от простого к сложному. Пишите каждое в отдельном файле и обязательно запускайте — понимание приходит от запуска, а не от чтения. Если что-то не выходит с первого раза, это норма: циклы «щёлкают» в голове не сразу.

1. Обратный отсчёт

Выведите числа от 10 до 1, а затем строку «Старт!».

Подсказка: два пути. Через range с отрицательным шагом (range(10, 0, -1)) или через while со счётчиком, который уменьшается. Попробуйте оба — сравните, какой читается понятнее.

2. Сумма и среднее

Пользователь вводит пять чисел, по одному. Выведите их сумму и среднее арифметическое.

Подсказка: for с range(5), накопитель total = 0 до цикла, total += int(input(...)) внутри. Среднее — сумма делённая на 5. Помните, что input() всегда возвращает текст: без int(...) числа не сложатся, а склеятся.

3. Фильтр слов

Дан список words = ["кот", "программирование", "дом", "алгоритм", "я"]. Выведите только слова длиннее пяти букв и в конце сообщите, сколько их оказалось.

Подсказка: длина слова — len(word). Внутри цикла понадобится if, а счётчик — до цикла. Проверьте себя: ответ — 2 слова.

4. Угадай число

Программа загадывает число (пока просто впишите его в переменную, например secret = 7) и в цикле просит пользователя угадать. После каждой попытки говорит «больше» или «меньше». Когда угадали — поздравляет и заканчивает. Посчитайте и выведите число попыток.

Подсказка: здесь нужен while — заранее неизвестно, сколько будет попыток. Условие может быть while guess != secret, но тогда переменную guess надо чем-то инициализировать до цикла. Альтернатива — while True с break после угадывания. Счётчик попыток увеличивайте на каждой итерации.

5. Таблица умножения

Выведите полную таблицу умножения от 2 до 9 в виде столбиков: сначала все умножения на 2, потом пустая строка, потом на 3, и так далее.

Подсказка: два вложенных цикла. Внешний перебирает множитель от 2 до 9, внутренний — от 1 до 10. Пустая строка печатается в теле внешнего цикла, после внутреннего. Следите за отступами: именно они решают, сколько раз напечатается разделитель.

Со звёздочкой. Переделайте задание 3 так, чтобы длинные слова не печатались сразу, а собирались в новый список, который вы выведете после цикла. Понадобится long_words = [] до цикла и long_words.append(word) внутри — метод append добавляет элемент в конец списка. Подробно про списки — в статье Списки, словари и множества.

Мини-итог

  • Цикл — способ выполнить один и тот же кусок кода много раз, написав его один раз.
  • for элемент in набор: перебирает готовый набор — список, строку, range. Один проход называется итерацией.
  • range(5) даёт числа 0, 1, 2, 3, 4 — счёт с нуля, верхняя граница не входит. Это источник ошибки «на единицу», и её совершают все.
  • while условие: повторяет, пока условие истинно. Внутри обязана быть строка, которая когда-нибудь сделает условие ложным — иначе цикл бесконечен.
  • Из бесконечного цикла выходят по Ctrl+C. Это не поломка, просто нажмите и продолжайте.
  • Накопитель — главный приём: переменная создаётся до цикла, изменяется внутри, используется после. Если объявить её внутри — она будет обнуляться каждый раз.
  • break выходит из цикла целиком, continue пропускает одну итерацию.
  • Вложенные циклы перемножают количество повторений — 100 × 100 это уже 10 000 выполнений тела.
  • TypeError: 'int' object is not iterable означает «в числе нечего перебирать» — вероятно, забыли range.
  • Не изменяйте список, по которому идёте циклом.

Что почитать дальше: раздел «for Statements» в официальном учебнике Python и главу 4 «Automate the Boring Stuff with Python» Эла Свейгарта — книга бесплатно доступна онлайн и объясняет циклы на бытовых задачах.

Что дальше

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

Функции: как перестать повторять себя и навести порядок

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов