Операторы потока управления

В этой главе мы познакомимся с условным ветвлением и циклами в python.

Об отступах

Как и во многих других языках программирования исходных код python исполняется сверху вниз, но операторы потока управления (такие как условное ветвление и циклы) позволяют проектировать менее линейные программы. Для того, чтобы научиться применять такие операторы в python, необходимо держать в голове, что отступы (indent) в python имеют значения.

Во многих языках программирования отступы нередко используются для обозначения программных блоков, например, тела функции, цикла или условного оператора. Ниже приводится два варианта одно и той же функция для вычисления суммы положительных элементов массива на языке C/C++, но во втором из них расставлены отступы, а в первом нет.

С отступами

double sum_positive(double* a, int n) {
double s = 0;
for (int i = 0; i < n; ++i) {
if (a[i] > 0)
s += a[i];
}
double s;
}

Без отступов

double sum_positive(double* a, int n) {
    double s = 0;
    for (int i = 0; i < n; ++i) {
        if (a[i] > 0)
            s += a[i];
    }
    double s;
}

Отступы не влияют на ход исполнения программы в C/C++ и с точки зрения компилятора эти две функции абсолютно эквивалентны, но хороший программист расставляет их в любом случае. Это позволяет проще ориентироваться в программе и значительно повышает её читабельность. Например, к какому if относится else в следующем примере?

if(x > 0)
    if(y > 0)
        std::cout << "if";
else
    std::cout << "else";

При первом взгляде, кажется, что блок else должен выполниться, если x <= 0, т.е. else относится к внешнему оператору if. На самом деле это не так! Если расставлять отступы корректно, то такой путаницы будет гораздо меньше.

if(x > 0)
    if(y > 0)
        std::cout << "if";
    else
        std::cout << "else";

Если в C/C++ отступы оставляются на усмотрение программиста, то в python они являются обязательными. Создатель python Гвидо Ван Россум при проектировании языка решил вынудить программистов расставлять отступы правильно. Не следующий этим требованиям код, не запустится вообще или произведет не те действия, которые от него ожидал программист.

Составные операторы в python

Операторы потока управления в python являются составными. Каждый такой оператор состоит из заголовка и тела (блок инструкций). Во многих других языках программирования для обозначения тела таких операторов используются скобки. Например, в C/C++ за это отвечают фигурные скобки. В python же для этих целей используется отступ: инструкции в теле составного оператора вносятся с одинаковым дополнительным отступом вправо относительно заголовка этого оператора.

инструкция снаружи составного оператора
заголовок составного оператора:
    первая инструкция внутри составного оператора 
    вторая инструкция внутри составного оператора
    ...
    последняя инструкция внутри составного оператора
инструкция снаружи составного оператора

Note

В конце заголовка составного оператора обязательно ставится символ двоеточия “:”.

Интерпретатор python понимает, что относится к телу составного оператора, на основе отступов.

  • Пока он встречает постоянный отступ вправо после заголовка составного оператора, он считает, что эти инструкции относятся к нему.

  • Как только отступ возвращается на прежний уровень, интерпретатор понимает, что он покинул тело составного оператора.

Note

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

Условное ветвление. if

Начнем с оператора условного ветвления. Ниже представлен код C/C++ и python, который решает задачу fizz buzz.

C/C++

if (i % 15 == 0)
    std::cout << "FizzBuzz, ";
else if (i % 3 == 0)
    std::cout << "Fizz, ";
else if (i % 5 == 0)
    std::cout << "Buzz, ";
else
    std::cout << i << ", ";

python

if i % 15 == 0:
    print('FizzBuzz, ')
elif i % 3 == 0:
    print('Fizz, ')
elif i % 5 == 0:
    print('Buzz, ')
else:
    print(f'{i}, ')
  1. Заголовок оператора условного состоит из ключевого слова if и условия. Синтаксическая разница с C/C++ заключается в том, что

    • условие необязательно помещать (и обычно не помещают) в круглые скобки;

    • после условия обязательно ставить двоеточие.

  2. После заголовка следует тело, т.е. блок инструкций, который выполняется если условие истинно. Синтаксическая разница с C/C++ заключается в том, что

    • в c++ для обозначения этого блока используются фигурные скобки, отступ этого блока расставляется программистом по его усмотрению;

    • в python для обозначения этого блока обязательно используется отступ.

  3. После тела опционально можно использовать ключевые слова else и/или elif, последнее из которых является сокращением от else if. После каждого из таких опциональных ветвлений else/elif должен следовать свой блок инструкций, который также обозначается отступом вправо.

Итого, оператор условного ветвление имеет приблизительно следующий вид:

if условие:
    первая инструкция блока if
    ...
    последняя инструкция блока if
elif условие:
    первая инструкция блока elif
    ...
    последняя инструкция блока elif
else: 
    первая инструкция блока else
    ...
    последняя инструкция блока else

Note

Блоков elif может быть произвольное количество, в том числе и ни одного.

Если вычисление выражения после ключевого слова if или elif даёт True, то соответствующий блок инструкций выполняется, если False, то этот блок инструкций игнорируется и выполняется блок инструкций else, если таковой указан. Если же вычисление этого выражение даёт небулевое значение, то python пытается его к таковому привести. Так, например, нулевые числа любого типа приводятся к False, а все остальные к True, что позволяет проверять на нулевое значение следующим образом.

from random import randint


random_value = randint(0, 1)                    # 0 или 1
print(f"Результат генерации: {random_value}")

if random_value == 1:
    print("Орёл")
else:
    print("Решка")

# или так

if random_value:
    print("Орёл")
else:
    print("Решка")
Результат генерации: 1
Орёл
Орёл

При этом в python второй вариант считается предпочтительнее.

Пустые коллекции приводятся к False, а все непустые к True.

l = []
if l:
    print("Список не пуст")
else:
    print("Список пуст")
Список пуст

Составные условия можно записывать с выражениями, включающими логические операции and и or.

  • В выражении x and y сначала вычисляется значение x и только если оно истинно (True), то вычисляется y;

  • В выражении x or y сначала вычисляется значение x и только если оно ложно (False), то вычисляется y;

Это позволяет записать выражения вида:

if x != 0 and y / x > 0:
    print("do something")

или

if x == 0 or y / x > 0:
    print("do something")

Note

Скобки не обязательны, т.к. приоритет and и or ниже, чем у >, != и ==.

Цикл while

Пример цикла while в C/C++.

int i = 1;
while (i < 1000){
    std::cout << i;
    i *= 2;
}

В python тот же самый эффект достигается следующим кодом.

i = 1
while i < 1000:
    print(i)
    i *= 2

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

while условие:
    первая инструкция тела цикла
    ...
    последняя инструкция тела цикла

Note

В python нет аналогам операторам инкремента ++ и декремента --, зато все остальные операторы +=, *=, /= дают схожий эффект.

Ключевые слова break и continue работают точно также, как и в C/C++:

  • break немедленно покидает цикл;

  • continue прекращает исполнение тела цикла на данной итерации и сразу заходит на следующую.

i = 0
while True:
    i += 1
    
    if i == 4:
        print("skip", end=", ")
        continue
    elif i == 8:
        print("end")
        break

    print(i, end=", ")
1, 2, 3, skip, 5, 6, 7, end

Цикл for

Цикл for в python совсем не похож на обычный цикл for в C/C++, но имеет много сходства с добавленным в C++11 range-based for.

Убедиться в этом можно сравнив следующее.

C/C++ код доступный по ссылке выше:

std::vector<int> v = {0, 1, 2, 3, 4, 5};
for (auto i : v) // compiler uses type inference to determine the right type
        std::cout << i << ' ';

и аналогичный код на python:

v = [0, 1, 2, 3, 4, 5]
for i in v:
    print(i, end=" ")
0 1 2 3 4 5 

Цикл for больше похож на цикл foreach, т.к. для его работы необходим итерируемый объект, по элементам которого пробегается цикл. В примере выше в качестве такого объекта выступает список v.

Цикл for тоже является составным оператором, который имеет приблизительно следующий вид.

for переменная in итерируемый_объект:
    первая инструкция тела цикла
    ...
    последняя инструкция тела цикла

Цикл for пробегается по элементам итерируемого объекта, указанного после ключевого слова in в заголовке цикла и перед двоеточием. При этом на каждой итерации текущий элемент доступен под именем, указанным в заголовке цикла после ключевого слова for и перед ключевым словом in.

Диапазон. range

Рассмотрим другой пример итерируемого объекта в python, который чаще всего используется для итерации по нему в цикле for.

Очень распространен случай, когда необходимо выполнить какие-то действия несколько раз. Для этих и некоторых других целей в python есть тип range (диапазоны).

Один аргумент

range(n) создаёт последовательность целых чисел от 0 включая до n не включая.

for i in range(5): 
    print(i, end=" ")
0 1 2 3 4 

Это позволяет, например, заменить все циклы из C/C++ вида

for(int i=0; i<n; i++){
    ...
}

на циклы в python вида

for i in range(n):
    ...

Два аргумента

range(start, stop) - диапазон целых чисел от start включая до stop не включая.

for i in range(-4, 5):
    print(i, end=" ")
-4 -3 -2 -1 0 1 2 3 4 

Это позволяет, например, заменить все циклы из C/C++ вида

for(int i=start; i<stop; i++){
    ...
}

на циклы в python вида

for i in range(start, stop):
    ...

Три аргумента.

range(start, stop, step) - то же, что и range(start, stop), но с шагом в step.

for i in range(0, 10, 2):
    print(i, end=" ")
0 2 4 6 8 

Это позволяет, например, заменить все циклы из C/C++ вида

for(int start=0; i<stop; i+=step){
    ...
}

на циклы в python вида

for i in range(start, stop, step):
    ...

Note

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

Коротко про функции

Объявление функции тоже составной оператор, который имеет приблизительно следующий вид.

def имя_функции(первый_параметр, ..., последний_параметр):
    первая инструкция в теле функции
    ...
    последняя инструкция в теле функции

Рассмотрим пример простой функции, считающий сумму квадратов её двух аргументов.

def sum_of_squares(x, y):
    square_x = x ** 2
    square_y = y ** 2
    return square_x + square_y

Здесь

  • sum_of_squares — имя функции,

  • x и y — имена её параметров,

  • square_x и square_y — локальные переменные функции, которые снаружи функции не видны.

При вызове функции результат вычисления выражения справа от return возвращается наружу. Если ключевого слова return не встречается, то возвращается значение None.

print(sum_of_squares(3, 4))
25

Практика

Максимальное значение списка

В качестве упражнения напишите функцию my_max, которая вычисляет максимум своего аргумента.

def my_max(array):
    return None


print(my_max([1, 10, 50, 200]))
print(my_max([200, 50, 10, 1]))
print(my_max([]))
Вычисление максимума в python
  1. Примените её к списку символов

    print(my_max(['a', 'b', 'A', 'B']))
    
  2. Сравните результат её работы с встроенной функцией max()

    print(max([1, 10, 50, 200]))
    print(max([200, 50, 10, 1]))
    print(max([]))
    

Note

Функция max() возвращает максимальный элемент списка (любого итерируемого объекта) или наибольшее значение из двух и более аргументов. Функция min() работает аналогично.