Инструкция ветвления и циклы

В данной лекции рассматривается инструкция ветвления (также называемая условной инструкцией и условным оператором) языка программирования Python, которая обеспечивает выполненение определенной последовательности команд в зависимости от некоторых условий. После нее мы поговорим о циклах - специальных инструкциях, позволяющих многократно выполнять один и тот же набор команд.

Содержание лекции

Инструкция ветвления

Инструкция ветвления в языке программирования Python имеет следующий вид:

if if_condition:
    if_code_block
elif elif_condition_1:
    elif_code_block_1
...
elif elif_condition_N:
    elif_code_block_N
else:
    else_code_block

Она должна содержать одно предложение if и может содержать ноль или более предложений elif, а также необязательное предложение else.

Каждый из компонентов сondition в инструкции ветвления представляет собой некоторое выражение, о котором можно сказать, является оно истинным (принимает значение True) или ложным (принимает значение False). Это означает, что в качестве condition может быть использовано любое выражение, результатом которого является значение с типом bool или значение, тип которого может быть неявно преобразован интерпретатором к типу bool.

Каждый из компонентов code_block представляет собой последовательность любых инструкций языка Python, в том числе инструкций ветвления и циклов. Обратите внимание, что все инструкции, входящие в эти блоки кода должны иметь одинаковый ненулевой отступ относительно стоящего сверху предложения if, elif или else. Общепринятым является использование четырех пробелов в качестве отступа. Среда разработки Jupyter Notebook упрощает написание кода, самостоятельно расставляя отступы нужного размера там, где требуется.

Принцип работы условной инструкции следующий:

  1. интерпретатор вычисляет выражение if_condition, и если оно истинно, выполняет все инструкции из if_code_block и завершает обработку инструкции ветвления
  2. если if_condition ложно, то интерпретатор по очереди (сверху вниз) вычисляет выражения elif_condition_k пока не встретит первое, которое будет истинно, а затем выполняет соответствующий блок кода elif_code_block_k и завершает обработку инструкции ветвления
  3. если ни одно из условий elif_condition_k не было истинным или предложения elif отсутствуют, интерпретатор выполняет else_code_block, если есть предложение else, и завершает обработку инструкции ветвления

Приведем простейший пример инструкции ветвления:

In [1]:
s1 = 'abc'
s2 = 'abc'

if s1 == s2:
    print('this string is printed only if s1 equals s2')
    
    print('this too')
print('this string is always printed')
this string is printed only if s1 equals s2
this too
this string is always printed

В данном примере мы намеренно не самым красивым образом оформили текст нашей программы, чтобы продемонстрировать важность правильной расстановки отступов при использовании инструкции ветвления. Первые две функции print имеют отступ относительно нее, и поэтому являются частью if_code_block, а следовательно выполняются только при истинности условия s1 == s2. Последняя функция print не имеет отступа относительно if, поэтому не является частью if_code_block и выполняется всегда независимо от истинности условия.

В следующем примере демонстрируется, как используется неявное преобразование типа из int в bool, которое выполняется интерпретатором потому, что выражение встречается в контексте, где ожидается булевое значение (мы упоминали кратко о таком преобразовании в главе Операции, но отложили его рассмотрение):

In [2]:
a = 13

# остаток от деления на 2 равен либо 1 (преобразуется в True), если число нечетное, 
# или 0 (преобразуется в False), если четное

if a % 2:
    print('a is odd')
else:
    print('a is even')
a is odd

Все возможности инструкции ветвления демонстрируются в следующем примере:

In [3]:
month_number = 5

if month_number > 2 and month_number < 6:
    print('it\'s spring') # нужно экранировать символ ' внутри строки
elif month_number > 5 and month_number < 9:
    print('it\'s summer')
elif month_number > 8 and month_number < 12:
    print('it\'s autumn')
else:
    print('winter is coming')
it's spring

Небольшие инструкции ветвления иногда заменяют условными выражениями, имеющими следующий синтаксис:

expression1 if condition else expression2

Результатом условного выражения становится результат выражения expression1, если codition равно True, или результат выражения expression2 в противном случае. Приведем пример условного выражения, возвращающего большее из двух чисел:

In [4]:
a = 10
b = 20
max_value = a if a > b else b
max_value
Out[4]:
20

Инструкции циклов

Цикл - это специальная конструкция в языках программирования, позволяющая организовать многократное выполнение одного и того же блока кода. В языке Python существуют две инструкции для организации циклов: while и for ... in.

while

Рассмотрим синтаксис инструкции while:

while condition:
    while_code_block
else:
    else_code_block

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

Рассмотрим принцип работы инструкции while:

  1. интерпретатор вычисляет выражение condition, и если оно равно True, выполняет все инструкции из блока while_code_block, а затем повторяет действия этого пункта с начала
  2. если condition ложно, интерпретатор проверяет наличие предложения else, и если оно есть, выполняет блок else_code_block
  3. интерпретатор завершает выполнения цикла и переходит к следующей инструкции
In [5]:
a = 0

while a <= 5:
    print(a)
    a += 1
else:
    print('end')

print('next instruction')
0
1
2
3
4
5
end
next instruction

С циклами while в Python связана распространенная ошибка: если выражение condition никогда не возвращает False, то цикл выполняется вечно, следовательно программа "зависает":

In [ ]:
a = 0

# в следующем цикле мы забыли увеличить значение a, поэтому оно всегда будет 0, следовательно
# условие a < 10 всегда дает True и цикл выполняется вечно
while a < 10:
    print(a)

# эта строчка никогда не выполнится
print('all printed')

Напомним, что если вы допустили ошибку, в результате которой программа зависает, то можно прервать ее выполнение с помощью команды Interrupt в меню Kernel.

for ... in

Инструкция for ... in может использоваться только для специальных итерируемых (iterable) типов данных. Итерируемым называется тип, представляющий собой набор из множества элементов, к которым можно обращаться по отдельности в некотором порядке. Мы уже знакомы с одним итерируемым типом - это строковый тип str. Много других мы узнаем в лекции, посвященной коллекциям.

Рассмотрим синтаксис инструкции for ... in:

for expression in iterable:
    for_code_block
else:
    else_code_block

Как и для инструкций if и while, часть else инструкции for ... in является необязательной.

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

Опишем принцип работы инструкции for ... in:

  1. пока не все элементы итерируемого типа были обработаны циклом, интерпретатор берет следующий из них и присваивает его переменной, имя которой указано в expression, а затем выполняет блок for_code_block
  2. когда все элементы итерируемого типа были обработаны, интерпретатор проверяет наличие предложения else, и если оно есть, выполняет блок else_code_block
  3. интерпретатор завершает выполнения цикла и переходит к следующей инструкции

Рассмотрим пример, в котором мы дублируем каждый символ строки:

In [6]:
s = '0123456789'
result = '' # это переменная, в которую мы постепенно будем записывать продублированные символы из s

for symbol in s:
    result += symbol * 2 # symbol имеет тип str, используем строковую операцию дублирования

result
Out[6]:
'00112233445566778899'

Инструкции управления циклами

В Python существуют инструкции break и continue, предназаначенные для изменения последовательности выполнения команд цикла. Обе они могут использоваться только внутри инструкций циклов while и for ... in (в их code_block), в противном случае интерпретатор генерирует исключение SyntaxError:

In [7]:
a = 1
break
  File "<ipython-input-7-d945458bb9f0>", line 2
    break
         ^
SyntaxError: 'break' outside loop

Инструкция continue говорит интерпретатору, что вместо того, чтобы выполнить следующие после нее инструкции цикла, он должен перейти в его начало. Инструкция break означает, что нужно завершить выполнение цикла и перейти на следующую после него инструкцию в коде. Если программа выходит из цикла по инструкции break, то блок кода, написанный в части else, не выполняется.

In [8]:
s = 'test string. this part will not be processed'
result = ''

# копируем в result все символы, кроме буквы 't'
# если встречаем символ '.', то прекращаем обработку строки s

# обратите внимание, что continue и break является частью блока кода инструкций ветвления,
# потому что нам нужно, чтобы они выполнялись только при истинности соответствующих условий

for symbol in s:
    if symbol == '.':
        break     # по условию задачи, нам больше не нужно обрабатывать строку s, поэтому
                  # выходим из цикла
    
    if symbol == 't':
        continue  # нам не нужна эта буква, поэтому мы не хотим, чтобы следующая строчка была
                  # выполнена и даем интерпретатору команду вернуться в начало цикла
    
    result += symbol
else:
    print('whole string processed') # сообщение, что строка s полностью обработана НЕ будет
                                    # выведено, если программа вышла из цикла с помощью break

print(result) # на эту строчку мы переходим, когда заканчивается выполнение инструкции for
es sring

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

if symbol != 't':
    result += symbol

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

while main_condition:
    do something

    if error_occurred:
        break

    do something

    if error_occurred:
        break
    ...

В заключение отметим, что инструкции break и continue оказывают влияние только непосредственно на тот цикл, внутри которого они встречаются. Рассмотрим такой пример:

In [9]:
a = 0
b = 0

while a < 3:
    print('a =', a)
    a += 1
        
    while b < 3: # инструкция break, которая идет внутри этого цикла, оказывает влияние только на него
        print('b =', b)
        break
        b += 1
a = 0
b = 0
a = 1
b = 0
a = 2
b = 0

Как только во внутреннем цикле выполняется инструкция break, интерпретатор завершает его работу, то есть инструкция b += 1 не обрабатывается. Мы говорили, что после завершения цикла интерпретатор переходит на следующую после него инструкцию. В случае, когда break встречается во вложенном цикле, следующей инструкцией становится либо следующая после вложенного цикла инструкция внешнего цикла, либо, если такой инструкции нет (как в нашем случае), интерпретатор переходит в начало внешнего цикла.

Вопросы для самоконтроля

  1. Что такое цикл? Какие инструкции циклов есть в Python?
  2. Что такое итерируемый тип данных? Приведите пример.
  3. Что такое вложенный цикл?
  4. С помощью какой инструкции можно прервать выполнение цикла?

Задание

  1. Программа в примере про времена года содержит потенциальную ошибку - найдите опасный участок кода и исправьте его.
  2. Перепишите пример с поиском корней квадратного уравнения из задания предыдущей лекции таким образом, чтобы корни вычислялись только в том случае, если дискриминант неотрицателен.
  3. Напишите программу, которая создает копию некоторой строки текста на английском языке, в которой отсутствуют все гласные буквы ("a", "e", "i", "o", "u"). Для решения задачи используйте два цикла for ... in, один из которых вложен в другой. Внешний цикл for ... in проходит по всем символам превоначальной строки, а внутренний - по специальной строке, содержащей только гласные. В результате выполнения внутреннего цикла можно определить, присутствует ли текущий символ среди гласных символов, и принять на основании этого решение, добавлять его в результат или нет.