4.1. Теория

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

  • ветвление (условная конструкция);
  • цикл - конструкция, предназначенная для организации многократного исполнения набора инструкций.

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

  • if: условное ветвление;
  • while: цикл с условием;
  • for: совместные циклы (циклы по коллекциям).

4.1.1. Условный оператор

Оператор if позволяет выполнять часть программы при наступлении определенного условия.

if
if logical_expression_1:     # (if строго 1) условие для проверки
    suite_1                  # блок команд для выполнения, если условие истинно
elif logical_expression_2:   # (elif - 0 или несколько) дополнительные условия
    suite_2                  # проверка идет, если условия выше ложны
elif logical_expression_N:
    suite_N
else:                        # (else - 0 или 1) блок команд для выполнения,
    else_suite               # если все условия выше оказались ложными

Ход выполнения:

  • каждое из условий logical_expression_N по очереди проверяется на истинность; условия - выражения типа bool, например, x > 2, a > 5 and b < 7 и т.д.;
  • как только истинное условие найдено, выполняется соответствующий блок suite_N, после чего осуществляется выход из всей условной конструкции (прочие варианты не проверяются и не выполняются);
  • если ни одно из условий не истинно («не срабатывает»), выполняется блок else (при наличии).

Для небольших условий возможно использование специального сокращенного варианта:

expression_1 if logical_expression else expression_2

Пример использования условного оператора приведен в Листинге 4.1.1.

Листинг 4.1.1 - Условный оператор if в Python | скачать
# Покупка в аптеке с подсчетом стоимости в зависимости от:
# - наличия социальной карты студента;
# - количества товара.

a_count = int(input("Сколько аскорбинок хотите купить? "))
has_social_discount = input("Есть ли у Вас социальная карта? ").upper() in ("1", "Y", "ДА")

price = 15.35  # Цена 1 аскорбинки
total_base = price * a_count  # Первоначальная стоимость
total = total_base  # Общая стоимость после применения скидок

print("\nЧек")
print('-' * 5)

if 0 < a_count <= 5:
    # Продаем по обычной цене
    print("Продаем по обычной цене")
elif a_count < 10:
    # До 10 аскорбинок даем скидку в 5%
    total *= 0.95
    print("Вам положена скидка в 5%!")
else:
    # Если больше 10 - каждая 10-я аскорбинка - бесплатно!
    free_count = a_count // 10
    total -= free_count * price
    print("Для Вас каждая 10-я аскорбинка будет бесплатной!")

# Если есть соц. карта - делаем скидку в 10% и отбрасываем копейки
if has_social_discount:
    total = int(total * 0.9)

# Форматная строка для "красивого" вывода чека
BILL_ITEM_FORMAT = "{message:<15} {value:>8.2f} р."

print("Соц. карта:", "Да" if has_social_discount else "Нет")
print(BILL_ITEM_FORMAT.format(message="Сумма покупки", value=total_base))
print(BILL_ITEM_FORMAT.format(message="Скидка", value=total_base - total))
print(BILL_ITEM_FORMAT.format(message="Итого", value=total))

# -------------
# Пример вывода:

# Сколько аскорбинок хотите купить? 7
# Есть ли у Вас социальная карта? Да
#
# Чек
# -----
# Вам положена скидка в 5%!
# Соц. карта: Да
# Сумма покупки      91.00 р.
# Скидка             16.45 р.
# Итого              91.00 р.

Примечание

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

4.1.2. Циклы

4.1.2.1. Цикл с условием

Цикл с условием представлен в Python оператором while.

while
while logical_expression:  # Если условие истинно, выполняется 'while_suite'
    while_suite            # После выполнения, происходит возврат к проверке
else:                      # (else - 0 или 1)
    else_suite             # Выполняется, если не было прерывания цикла

Особенности:

  • блок команд while_suite выполняется пока logical_expression истинно;
  • блок else необязателен, а его содержимое (блок else_suite) выполняется только, если не было прерывания цикла.

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

Пример использования цикла while приведен в Листинге 4.1.2.

Листинг 4.1.2 - Цикл while в Python | скачать
# Использование цикла с условием для охлаждения напитка
#
# Считаем, что напиток холодный при температуре <= 10

TEMP_COLD_MAX = 10
temp = 20

while temp > TEMP_COLD_MAX:
    print("Кола при температуре {}°C - так себе! ".format(temp) +
          "Кладу кусочек льда...")
    temp -= 1

print("Отлично, при {}°C можно пить!".format(temp))

# -------------
# Пример вывода:

# Кола при температуре 20°C - так себе! Кладу кусочек льда...
# Кола при температуре 19°C - так себе! Кладу кусочек льда...
# Кола при температуре 18°C - так себе! Кладу кусочек льда...
# Кола при температуре 17°C - так себе! Кладу кусочек льда...
# Кола при температуре 16°C - так себе! Кладу кусочек льда...
# Кола при температуре 15°C - так себе! Кладу кусочек льда...
# Кола при температуре 14°C - так себе! Кладу кусочек льда...
# Кола при температуре 13°C - так себе! Кладу кусочек льда...
# Кола при температуре 12°C - так себе! Кладу кусочек льда...
# Кола при температуре 11°C - так себе! Кладу кусочек льда...
# Отлично, при 10°C можно пить!

Пример использования ключевого слова else приводится в разделе Прерывание и продолжение циклов.

Примечание

Бесконечный цикл

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

4.1.2.2. Совместный цикл (цикл по коллекциям)

Совместный цикл представлен в Python циклом for.

for
for expression in iterable:  # Для каждого элемента 'expression' из 'iterable'
    for_suite                # выполняется 'for_suite'
else:                        # (else - 0 или 1)
    else_suite               # Выполняется, если не было прерывания цикла

Особенности:

  • в качестве iterable может использоваться любой итерируемый объект (список, словарь и др.);
  • блок команд for_suite выполняется для каждого элемента expression из iterable; при этом внутри блока for_suite expression содержит ссылку на текущий просматриваемый элемент;
  • блок else необязателен; при наличии выполняется блок else_suite, если не было прерывания цикла.

Пример использования for приведен в Листинге 4.1.3.

Листинг 4.1.3 - Цикл for в Python | скачать
# 1. Простое перемещение по коллекции (списку)
#
#    Выводим на экран наименование языка программирования из списка и его длину
langs = ['С++', 'Java', 'Python']
for lang in langs:
    print(lang, len(lang))
print()

# 2. В Python в явном виде отсутствует привычный для Паскаля или Си
#    цикл со счетчиком. Однако данный тип цикла может быть реализован как
#    for ... in с перемещением по числовому диапазону range(...)
#
#    Выводим на экран числа от 1 до 10
for i in range(10):
    print(i + 1, end=" ")

# -------------
# Пример вывода:

# С++ 3
# Java 4
# Python 6
#
# 1 2 3 4 5 6 7 8 9 10

Пример использования ключевого слова else приводится в разделе Прерывание и продолжение циклов.

4.1.2.2.1. Итераторы

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

Общая схема взаимодействия коллекции и итератора следующая:

  • коллекция и итератор взаимно ссылаются друг на друга через специальный метод __iter__();
  • итератор предоставляет дополнительный метод __next__() для получения следующего элемента привязанной коллекции (Рисунок 4.1.1).
_images/04_01_01.png

Рисунок 4.1.1 - Итератор и итерируемый объект [6]

Итераторы, реализованные по-разному для различных классов (например, для кортежа и словаря) позволяют циклу for перемещаться по коллекциям, не заботясь о внутренней структуре объектов, а используя методы __iter__() и __next__(). Вызов __next__() завершается возбуждением исключения StopIteration, означающим, что элементов в коллекции больше нет (Листинг 4.1.4).

Листинг 4.1.4 - Пример явного использования итератора для списка и словаря
# 1. Явное использование итератора для списка

>>> my_list = [1, 2, 3]

>>> it = my_list.__iter__()
>>> it
<list_iterator object at 0x000001E8E20BAB00>

>>> it.__next__()
1
>>> it.__next__()
2
>>> it.__next__()
3

>>> b.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

# 2. Явное использование итератора для словаря

>>> my_dict = dict(name="Саша", age=20, weight=65.6)
>>> it = my_dict.__iter__()
>>> it
<dict_keyiterator object at 0x000001E8E20B04F8>

>>> it.__next__()
'age'
>>> it.__next__()
'name'
>>>
>>> it.__next__()
'weight'

>>> it.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

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

all(iterable)

Возвращает True, если все элементы iterable в логическом контексте оцениваются как True.

any(iterable)

Возвращает True, если хотя бы 1 элемент iterable в логическом контексте оцениваются как True.

enumerate(iterable, start=0)

Возвращает итератор, где каждый элемент является парой «номер» - «значение». Номер отсчитывается от start. Обычно используется в циклах for, чтобы получить последовательность кортежей (номер, элемент).

sorted(iterable, key=None, reverse=False)

Возвращает отсортированный объект в виде списка для итерируемого объекта iterable.

Параметры:
  • key – функция сортировки (по умолчанию не учитывается, сортировка осуществляется поэлементно);
  • reverse – если равен True, сортировка осуществляется в обратном порядке.
reversed(iterable)

Возвращает итератор, возвращающий элементы в обратном для исходного iterable порядке.

Пример использования нескольких функций приведен в Листинге 4.1.5.

Листинг 4.1.5 - Функции для работы с итерируемыми объектами
# Использование специальных функций для итерируемых объектов

>>> fruits = ["апельсины", "ябоки", "мандарины", ""]

>>> any(fruits)
True

>>> all(fruits)  # False (пустая строка == False)
False

>>> list(reversed(fruits))
['', 'мандарины', 'ябоки', 'апельсины']

>>> list(enumerate(fruits, start=1))
[(1, 'апельсины'), (2, 'ябоки'), (3, 'мандарины'), (4, '')]

4.1.2.2.2. Некоторые техники перемещения по коллекциям

Python предоставляет широкие возможности по организации циклов. В Листинге 4.1.5 приведены некоторые часто используемые случаи с различными типами коллекций.

Листинг 4.1.6 - Техники организации перемещения по коллекциям в Python | скачать
# 1. Последовательности

# Используя enumerate(), можно "дать" порядковый номер элементу коллекции в цикле
for i, item in enumerate(['камень', 'ножницы', 'бумага']):
    print(i, item)

# Вывод:
# ------
# 0 камень
# 1 ножницы
# 2 бумага

# Используя sorted(), можно вывести элементы коллекции в порядке возрастания
for item in sorted(['камень', 'ножницы', 'бумага']):
    print(item)

# Вывод:
# ------
# бумага
# камень
# ножницы

# Цикл со счетчиком в обратную сторону можно организовать, используя
# reversed() или range() с параметрами
# Следующие 2 цикла выводят одинаковые значения
for i in reversed(range(10)):
    print(i)  # Числа от 9 до 0 на отдельной строке

for i in range(9, 0, -1):
    print(i)  # Числа от 9 до 0 на отдельной строке

# 2. Словари

sportsman = {"фио": "Федоров Сергей Викторович",
             "вид_спорта": "хоккей",
             "дата_рождения": "13.12.1968"}

# По умолчанию цикл for перемещается по ключам
# enumerate() и sorted() аналогично работают только с ключами
for attr in sorted(sportsman):
    print(attr)  # Получить значение по ключу - sportsman[attr]

# Вывод:
# ------
# вид_спорта
# дата_рождения
# фио

# Используя items() можно сразу получать пару ключ-значение
for attr, value in sportsman.items():
    print(attr, ":", value)

# Вывод:
# ------
# фио : Федоров Сергей Викторович
# вид_спорта : хоккей
# дата_рождения : 13.12.1968

4.1.2.3. Прерывание и продолжение циклов

break, continue

Любая циклическая конструкция может быть немедленно прервана или продолжена (осуществлен переход к проверке условия или следующей итерации в циклах while и for соответственно).

Прерывание цикла может произойти при:

  • наличии команды break: приводит к выходу из цикла (только из того, внутри которого она написана);
  • наличии команды return: выход из функции (и из цикла, соответственно, Тема №5);
  • возникновении исключения (Тема №7).

Не зависимо от способа прерывания цикла, механизм действует одинаково - выполнение цикла прерывается, дополнительная часть цикла else не выполняется и осуществляется выход за пределы цикла.

Для продолжения цикла используется команда continue, передающая управление в начало цикла для выполнения следующей проверки или итерации.

Примеры прерывания и продолжения цикла приведены в Листингах 4.1.7 и 4.1.8.

Листинг 4.1.7 - Прерывание цикла, используя оператор break в Python (для цикла while механизм аналогичен) | скачать
# Прерывание цикла может оказаться полезным, если задача решена и продолжение цикла не нужно
#
# Ищем первое четное число в списке
# При нахождении имеет смысл выйти из цикла и не просматривать его дальше впустую

nums = [1, 6, 8, 2, 9, 3, 5]

for x in nums:
    if x % 2 == 0:
        print("Число найдено: {}".format(x))
        break
else:
    print("Четное число не найдено!")

# -------------
# Пример вывода:

# Число найдено: 6
Листинг 4.1.8 - Продолжение цикла, используя оператор continue в Python (для цикла while механизм аналогичен) | скачать
# Прерывание цикла может оказаться полезным, если задача решена и продолжение цикла не нужно
#
# Запрашиваем у пользователя 5 целых чисел и считаем сумму четных
# Данную задачу можно также решить и выполняя сложение при условии без continue

sum_x = 0
for i in range(5):
    x = int(input("Введите число №{}: ".format(i + 1)))
    if x % 2 != 0:
        continue

    sum_x += x

print("Сумма =", sum_x)

# -------------
# Пример вывода:

# Введите число №1: 1
# Введите число №2: 2
# Введите число №3: 3
# Введите число №4: 4
# Введите число №5: 5
# Сумма = 6

4.1.3. Комбинация циклов и условий

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

Циклические конструкции можно произвольно комбинировать для достижения нужного эффекта - тем самым создавая вложенные друг в друга циклы; 2 цикла называются внешним и внутренним (или вложенным) соответственно, когда второй находится внутри первого. Количество уровней вложенности, как правило, не ограничивается.

В Листингах 4.1.9 и 4.1.10 приведены примеры вложенности циклов и комбинации циклов и условий.

Листинг 4.1.9 - Вложенные циклы в Python | скачать
# Дан список чисел, найти НОД каждого из них с введенным числом

nums = [12, 86, 44, 24, 73, 19]
print(nums)

x = int(input("Введите второе число для поиска НОД: "))

for num in nums:
    # Поиск НОД по алгоритму Евклида https://ru.wikipedia.org/wiki/Алгоритм_Евклида
    temp1, temp2 = num, x
    while temp1 != temp2:
        if temp1 < temp2:
            temp2 -= temp1
        else:
            temp1 -= temp2

    print("Числа {} и {}, НОД = {}".format(num, x, temp1))

# -------------
# Пример вывода:

# [12, 86, 44, 24, 73, 19]
# Введите второе число для поиска НОД: 12
# Числа 12 и 12, НОД = 12
# Числа 86 и 12, НОД = 2
# Числа 44 и 12, НОД = 4
# Числа 24 и 12, НОД = 12
# Числа 73 и 12, НОД = 1
# Числа 19 и 12, НОД = 1
Листинг 4.1.10 - Комбинация циклов и условий в Python | скачать
# Поиск простых чисел от 1 до 10
#
# Внешний цикл отвечает за перебор чисел от 1 до 10
# Внутренний цикл подсчитывает количество делителей, перебирая
# все числа от 2 до текущего числа - 1
#
# Данная задача выглядит проще, если решить ее с использованием 2 циклов for,
# данный вариант приведен для демонстрации вложенности разных видов циклов

for i in range(1, 11):
    divisors = 0
    j = 2
    # Внутренний цикл выполняется пока не переберутся все числа [2; i] или
    # не найдется хотя бы 1 делитель
    while j < i and divisors == 0:
        if i % j == 0:
            divisors += 1

        j += 1

    # Если делители для [i] не найдены - это простое число
    if divisors == 0:
        print(i, end=" ")

# -------------
# Пример вывода:

# 1 2 3 5 7

При написании вложенных циклов необходимо помнить:

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

4.1.4. Коллекционные включения

Python поддерживает специальный компактный синтаксис создания коллекций, называемый коллекционным включением (comprehensions).

Общий вид (на примере списков):

# Просто коллекционное включение:
# "Составить список из 'expression' для каждого
#  элемента 'item' в итерируемом объекте 'iterable'"
[expression for item in iterable]

# Коллекционное включение с условием:
# "Составить список из 'expression' для каждого
#  элемента 'item' в итерируемом объекте 'iterable'
#  есдт соблюдается условие 'condition'"
[expression for item in iterable if condition]

В Листинге 4.1.11 приведен пример компактного создания отдельных коллекций.

Листинг 4.1.11 - Коллекционные включения в Python | скачать
# Коллекционные включения позволяют эффективнее создавать коллекции

# 1. Формирование списка чисел от 1 до 100, оканчивающихся на 1

# Стандартный способ
nums = tuple(range(100))

lst = []
for i in nums:
    if i % 10 == 1:
        lst.append(i)

# Списковое включение
lst2 = [i for i in nums if i % 10 == 1]

print(lst == lst2)  # True

# 2. В Python коллекционные включения также доступны и для других типов

# Словарь
# Определяем слова и их длину
words = "Без труда не вытащишь и рыбку из пруда"

stats = {w.lower(): len(w) for w in words.split()}
print(stats)  # {'вытащишь': 8, 'рыбку': 5, 'не': 2, 'пруда': 5,
              #  'и': 1, 'из': 2, 'без': 3, 'труда': 5}

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


[1]Sebesta, W.S Concepts of Programming languages. 10E; ISBN 978-0133943023.
[2]Python - официальный сайт. URL: https://www.python.org/.
[3]Python - FAQ. URL: https://docs.python.org/3/faq/programming.html.
[4]Саммерфилд М. Программирование на Python 3. Подробное руководство. — М.: Символ-Плюс, 2009. — 608 с.: ISBN: 978-5-93286-161-5.
[5]Лучано Рамальо. Python. К вершинам мастерства. — М.: ДМК Пресс , 2016. — 768 с.: ISBN: 978-5-97060-384-0, 978-1-491-94600-8.
[6]Итератор и итерируемый объект. URL: http://nvie.com/img/iterable-vs-iterator.png.