2.3. Циклы

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

В питоне есть циклы двух типов.

(Я тут буду приводить примеры кодов. Конечно, экспериментируйте с ними для лучшего понимания.)

2.3.1. Цикл while

Цикл while — это простейший вариант цикла. Он выполняет некоторые действия, после чего определяет, не надо ли их выполнить еще раз. «Определяет» путем проверки указанного программистом условия. Выглядит код так:

while условие:
    код

Здесь условие — это условие в том же виде, в котором вы пишете условие if’а. Там могут быть сравнения, and, or и т.д.

А код — это произвольная последовательность комад (может занимать и несколько строк), содержащая любые команды, которые вы знаете или узнаете потом: присваивания, if’ы, даже другие циклы и т.д.

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

Как может условие выполняться-выполняться, и вдруг перестать выполняться? Очень просто: код может изменить какие-то переменные, из-за которых условие перестанет выполняться. Собственно, в этом и состоит весь смысл процесса. (А если код ничего такого не меняет, то условие будет выполняться всегда, и получится «бесконечный цикл» — программа зависнет. Не делайте так.)

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

Слово «while» переводится как «пока», т.е. вся запись цикла по сути обозначает «пока выполняется условие, делай код».

2.3.1.1. Примеры

n = int(input())
a = 1
while 2 * a < n:
    print(a)
    a += 1
print("Done")

Что делает этот код? Он сначала считывает с клавиатуры значение n. Пусть для примера пользователь вводит число 5. Далее в переменную a записывается 1. А дальше начинается цикл.

Сначала компьютер проверяет, правда ли, что 2 * a < n. Действительно, 2*a равно 2, а n равно 5, условие выполняется. Поэтому начинаем выполнять тот код, который написан внутри цикла. А именно, выводим на экран a, т.е. 1. И прибавляем единицу к a, получается a==2. Код (говорят «тело цикла») закончился, поэтому проверяем условие еще раз. Оно все еще выполняется (2*a равно 4, а n равно 5), поэтому выполняем тело цикла заново. Выводим на экран 2 и увеличиваем a еще на единицу. Проверяем условие еще раз, оно уже не выполняется (2*a равно 6, а n равно 5), цикл закончился.

Цикл закончился, поэтому переходим на то, что после цикла — выводим на экран Done.

Еще один пример, уже довольно навороченный:

n = int(input())
a = 1
while a < n:
    b = a
    while b > 0:
        if b % 2 == 1:
            print(b, end=" ")
        b -= 1
    print()
    a *= 2

Здесь цикл, вложенный в цикл. Это работает следующим образом. Пусть пользователь ввел 6. Переменная a становится равна 1.

Начинается внешний цикл (который while a < n). Переменная b становится равна a, т.е. 1. Пока b>0 мы делаем следующее (внутренний цикл): если b нечетное, то выводим на экран 1, после чего уменьшаем b на 1. В итоге на экран выведется 1, после чего b станет равно 0, и внутренний цикл закончится.

Но внешний цикл еще продолжается. Выполняется команда print(), которая просто переводит строку, и переменная a увеличивается в 2 раза и становится равна 2. Проверяем условие цикла: a все еще меньше n. Поэтому повторяем операции, но уже с новым a. Переменная b становится равна 2, начинается внутренний цикл, сначала (при b==2) на экран ничего не выводится (т.к. b четное), и b становится равно 1, потом на экран выводится 1, и b становится равно 0 и внутренний цикл заканчивается.

Продолжается внешний цикл, выводится перевод строки и a становится равно 4. Это все еще меньше чем n, поэтому входим заново во внутренний цикл, b становится равно 4 … и за весь внутренний цикл на экран выводится 3 1 (я не буду уже подробно расписывать).

Далее опять выводим перевод строки, a становится равно 8, это уже больше чем n, поэтому внешний цикл закончился.

2.3.2. Цикл for

Цикл while работает тупо: проверяет условие и выполняет код, и так пока условие не перестанет выполняться. Это позволяет реализовать практически любые правила зацикленности, какие нужны в задаче, и потому часто применяется.

Но кроме того, довольно часто бывает так, что надо выполнить один и тот же код несколько раз подряд, просто изменяя значения одной переменной некоторым очень простым образом. Для этого есть цикл for. Он пишется так:

for переменная in список_значений:
    код

Этот цикл работает так: указанной переменной присваивается первое значение из списка, и выполняется код. Потом ей присваивается следующее значение, и так далее.

Пример:

for i in 7, 42, 137:
    print(i)

Этот код выведет на экран по очереди все три указанных числа (7, 42 и 137).

Список значений можно задавать как в примере выше, через запятую, а можно и разными другими способами. Общие правила тут вы узнаете позже, пока просто приведу наиболее распространенный вариант, который вам сейчас чаще всего будет нужен (а вариант с явным перечислением значений, как выше, вам сейчас довольно редко будет нужен).

А именно, очень часто вам надо, чтобы переменная цикла менялась, перебирая числа в некотором диапазоне по порядку, например, 1, 2, 3, 4, …, 10. Для этого есть конструкция range. Пишется так: for i in range(1, 11) — это перебирает все числа от 1 (включительно) до 11 (невключительно), т.е. как раз написанный выше набор чисел. Еще раз, потому что важно: первое число включительно, второе невключительно. Пример:

for i in range(1, 21):
    print(i, "*", i, "=", i * i)

выводит на экран таблицу квадратов всех чисел от 1 до 20 включительно (или до 21 невключительно).

У команды range можно не указывать первый параметр, тогда он будет считаться равным 0: for i in range(4) переберет числа 0, 1, 2, 3. Это может показаться странным и непоследовательным, но в следующей теме (про массивы) вы поймете, что это очень естественно.

И наоборот, у команды range можно указать третий параметр — шаг, с которым будет меняться значение переменной. Например, range(1, 7, 2) обозначает «от 1 (включительно) до 7 (невключительно) с шагом 2», т.е. дает числа 1, 3, 5. Или range(0, 100, 10) дает числа 0, 10, 20, 30, …, 90.

Особое применение этого третьего параметра — это перебор чисел в обратном порядке. range(10, 0, -1) дает 10, 9, 8, …, 1. Обратите внимание, что 0 опять не включается. (Аналогично можно указывать шаг -2 и т.п.)

В range можно, конечно, указывать и переменные, выражения и т.д. Например, range(a - b, a + b + 1) перебирает числа от a-b до a+b включительно (до a+b+1 невключительно).

И напоследок — еще один, более сложный, пример применения цикла for:

for i in range(1, 10):
    for j in range(1, 10):
        print(i * j, end="")
    print()

выводит на экран таблицу умножения.

2.3.3. Про команды break и continue

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

2.3.3.1. Понятие тела цикла и итерации

Сначала введу/повторю несколько терминов, которые полезны при обсуждении циклов.

Тело цикла — это собственно те команды, которые находятся внутри цикла. Например, в цикле

for i in range(1, n + 1):
    a = i * i
    print(i, a)

тело цикла состоит из двух команд: присваивания и вывода.

Итерацией называется каждый отдельный проход по телу цикла. Цикл всегда повторяет команды из тела цикла несколько раз — вот каждое такое повторение и называется итерацией. В примере выше можно сказать, что цикл сделает n итераций. Можно, например, сказать, что на пятой итерации цикла будет выведена строка «5 25».

2.3.3.2. Команда break

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

Пример:

for i in range(2, n + 1):
    if n % i == 0:
        print(i)
        break
    print('Попробовали', i, ', не подходит')
print('Конец!')

— как только условие if’а выполнится, на экран будет выведено соответствующее i, и выполнение цикла будет прервано — дальше будет выведено слово «Конец!» и т.д. При этом строка «Попробовали…» будет выводиться для всех i, не включая то, на котором выполнилось условие цикла.

Например, для n==9 вывод будет следующий:

Попробовали 2 , не подходит
3
Конец!

(Правда, данный конкретный код было бы проще написать через while — подумайте, как)

Команду break можно также применять и с циклами while и repeat, один из примеров будет ниже.

2.3.3.3. Команда continue

Команда continue обозначает прервать выполнение текущей итерации цикла и начать следующую итерацию. Т.е. как будто бы, не доделывая то, что написано ниже в теле цикла, прыгнуть на начало цикла, при этом выполнив все действия, которые должны быть выполнены после очередной итерации — т.е. в цикле for увеличив значение счетчика цикла на 1, а в циклах while/repeat проверив условие и, если оно не выполняется, то вообще прервав работу.

Пример:

for i in range(2, n):
    if n % i != 0:
        print('Попробовали', i, ', не подходит')
        continue
    print(n, 'делится на', i)

— здесь цикл пройдется по всем числам от 2 до n-1 и для каждого выведет, делится ли n на i или нет. Например, при n==9 вывод будет такой:

Попробовали 2 , не подходит
9 делится на 3
Попробовали 4 , не подходит
...
Попробовали 8 , не подходит

Пройдем подробнее по началу выполнения этого кода. Сначала i становится равным 2. Смотрим: 9 % 2 != 0 — значит, идем внутрь if. Выводим на экран «Попробовали…», и далее идет команда continue. Значит, сразу идем на следующую итерацию: увеличиваем i (!), оно становится равным 3, и идем на начало цикла. 9 % 3 == 0, поэтому в if не идем, выводим «9 делится на 3», итерация закончилась — увеличиваем i и идем на следующую итерацию. И так далее.

Конечно, в этом примере можно было бы обойтись и без continue, просто написать else. Это было бы проще. Но бывает, что вам надо перебрать числа, и есть много случаев, когда какое-то число вам не надо рассматривать. Тогда писать кучу else было бы намного сложнее, чем несколько continue. Например (пример выдуман из головы, но подобные случаи бывают):

for i in range(n):
    # нам не нужны числа, делящиеся на 5
    if i % 5 == 0:
        continue
    # нам не нужны числа, квадрат которых дает остаток 4 при делении на 7
    # обратите внимание, что мы можем делать какие-то действия до проверки условий
    p = i * i
    if p % 7 == 4:
        continue
    # все оставшиеся числа нам нужны,
    # поэтому здесь делаем какую-нибудь сложную обработку из многих команд
    ...

— тут намного более понятно, что вы имели в виду, чем если бы вы писали с else. С else тому, кто будет читать ваш код, пришлось бы смотреть, где else заканчивается, и вдруг после конца else идут еще какие-нибудь команды, а здесь все понятно: если if выполняется, то пропускается все оставшееся тело цикла.

2.3.3.4. while True и break

Один важный случай применения команды break состоит в следующем. Часто бывает так, что вам надо повторять какую-то последовательность действий, и проверять условие окончания вам хочется в середине этой последовательности. Например, вам надо считывать с клавиатуры числа, пока не будет введен ноль. Все числа, кроме нуля, надо как-то обрабатывать (для простоты будем считать, что выводить на экран — это нам не существенно).

Естественная последовательность действий следующая:

считать число
если ноль, то прерваться
вывести это число на экран
считать число
если ноль, то прерваться
вывести это число на экран
...

Очень четко видна цикличность, но если вы попытаетесь написать цикл без команды break, ничего хорошего у вас не получится.

У вас будет несколько вариантов: например, так

a = int(input())
while a != 0:
    print(a)
    a = int(input())

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

Второй вариант, который вам может придти в голову, такой:

a = 1
while a != 0:
    a = int(input())
    if a != 0:
        print(a)

Этот вариант лучше в том смысле, что каждая итерация работает только с одним числом, но у него все равно есть недостатки. Во-первых, есть искуственная команда a = 1 перед циклом. Во-вторых, условие a != 0 дублируется; если вам придется его менять, вы можете забыть, что оно написано в двух местах. В-третьих, у вас основная ветка выполнения цикла, ветка, по которой будет выполняться большинство итераций, попала в if. Это не очень удобно с точки зрения кода: все-таки все числа, кроме последнего, будут не нулевыми, поэтому хотелось бы написать такой код, в котором обработка случая a = 0 не потребует заворачивания основного варианта в if — так просто читать удобнее (особенно если бы у нас было бы не просто print(a), а существенно более сложный код обработки очередного числа, сам включающий несколько if’ов и т.п.).

Но можно сделать следующим образом:

while 0 == 0:
    a = int(input())
    if a == 0:
        break
    print(a)

Искусственная конструкция 0==0 — это условие, которое всегда верно: нам надо, чтобы while выполнялся до бесконечности, и мог бы завершиться только по break. На самом деле в питоне есть специальное слово True, которое обозначает условие, которое всегда верно (и симметричное слово False, которое обозначает условие, которое не верно никогда). Соответственно, еще лучше писать while True:

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

Вот так и следует писать любые циклы, в которых проверка условия нужна в середине тела цикла:

while True:
    что-то сделали
    if надо завершить работу:
        break
    сделали что-то еще