if, elif, else

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

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

Полный синтаксис конструкции выглядит так:

if <условие 1>:
    <блок кода 1>
elif <условие 2>:
    <блок кода 2>
else:
    <блок кода 3>

Для удобства проще всего читать такой код так: если (if) выполняется <условие 1>, то выполнить <блок кода 1>, иначе если (elif, сокращенно от else if) выполняется <условие 2> то выполнить блок кода 2, иначе (else) выполнить блок кода 3.

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

Условие — это логическое выражение которое в результате выполнения которого получается значение которое Python либо принимает за False или любое True.

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

n = input("Введите число: ")
if n.isnumeric():
    print("Вы ввели число")
# конец

Графическое представление такой конструкции с помощью блок-схемы. Для того чтобы его прочитать следите за направлениями движений стрелочек:

st=>start: n = input("Введите число: ")
e=>end: # конец
cond=>condition: if n.isnumeric()
io=>inputoutput: print("Вы ввели число")

st->cond
cond(no)->e(right)
cond(yes)->io->e

Более сложный пример:

n = input("Введите число: ")
if n.isnumeric():
    n = int(n)
    if n < 0:
        print("Отрицательное число")
    elif 0 <= n <= 10:
        print("Маленькое число")
    else:
        print("Большое число")
else:
    print("Ошибка")

print("Спасибо")
# конец

Давайте разберем подробнее этот пример. Мы просим ввести пользователя число и проверяем строку методом isnumeric(), если эта строка состоит из символов которые могут быть конвертированы в число, то проверка вернет True, и значит первый оператор if будет истинным. Тогда стартует большой вложенный блок который конвертирует строку в число и запустит дополнительные проверки. Но если пользователь введет какой-то текст который невозможно конвертировать в число, то сработает инструкция else и на экран выведется сообщение об ошибке ввода. В случае выполнения условий происходит вывод соответсвующего текста:

st=>start: n = input("Введите число: ")
cond_isnum=>condition: if n.isnumeric()
op_num=>operation: n=int(n)
cond_zero=>condition: if n < 0
io_below_zero=>inputoutput: print("Отрицательное число")
cond_0_to_10=>condition: elif 0<= n <= 10
io_small=>inputoutput: print("Маленькое число")
io_big=>inputoutput: print("Большое число")
io_error=>inputoutput: print("Ошибка")
io_thx=>inputoutput: print("Спасибо")
e=>end: # конец

st->cond_isnum
cond_isnum(no)->io_error->io_thx->e
cond_isnum(yes)->op_num->cond_zero
cond_zero(yes)->io_below_zero->io_thx->e
cond_zero(no)->cond_0_to_10
cond_0_to_10(yes)->io_small->io_thx
cond_0_to_10(no)->io_big->io_thx
io_thx->e

В этом примере показан пример более глубокого уровня вложенности. Внутри условия первого уровня появляется вложенный блок который находится на своем уровне и в нем появляются дополнительные отступы. На самом деле в Python может быть достаточно много уровней вложенности, но обычно если их больше 5-7, то это частый признак того что код надо оптимизировать. Потому что большое количество вложенностей может плохо сказаться на читаемости кода.

Комбинирование условий

В предыдущем примере мы уже столкнулись с примером того что число в переменной n проверяется на вхождение в диапазон от 0 до 10 такой конструкцией 0 <= n <= 10. Поскольку результат выполнения оператора — это логическое значение, то оно может быть комбинировано и с другими логическими операторами для того чтобы создавать сложные условия:

x = 'abc'

if 'a' in x and 'b' in x or 'c' in x:
    print("Некоторые буквы найдены")

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

x = 'abracadabra'

if (('ab' in x) and ('ba' in x)) or ('ca' in x):
    print("Некоторые буквы найдены")

Сначала идет проверка первого блока в котором оператор and сравнивает выполнение двух условий на вхождение подстроки ab и ba в переменную x, если эти условия не выполнены, то произойдет проверка второго блока условий после or и проверка на вхождение ca в x. Для того чтобы проверка была успешной достаточно выполнения обоих первых условий (между ними стоит И) или второго условия.

Блок условий — это полноценный код, который выполняется в момент проверки, этот код может содержать вычисления или вызовы функций:

n = 10

if n ** 2 >= 100:
    pass

Согласно приоритету операторов переменная n возводится в степень 2, а после этого происходит проверка неравенства.

Проверка на истинность

Во время работы с Python кодом вам часто будет встречаться конструкция:

if variable:
    # тело блока

Может показаться, что тут нет явного условия, но согласно правилам языка истинной являются все значения кроме None, 0, 0.0, 0j, '', (), [], {}. Поэтому если ваша переменная имеет хоть какое-то значение кроме перечисленных, то она автоматически становится равна True. Что позволяет писать такой лаконичный код.

Проверка на None

Противоположная ситуация у проверки на None. Формально есть два способа проверить равна ли переменная None:

  • С помощью ключевого слова is;
  • С помощью оператора ==;

В большинстве случаев обе проверки дадут одинаковый результат:

>>> null = None
>>> null is None
True
>>> null == None
True

Но на самом деле когда происходит проверка оператором ==, то под капотом интерпретатор вызывает специальную магическую функцию объекта __eq__. Это занимает дополнительное время на поиск функции в словаре объекта. is же сравнивает адреса переменных в памяти и поэтому работает очень быстро. Поэтому для проверки на None лучше использовать оператор is.

Перекрытие магического метода __eq__

Мы не изучаем работу магических функций в этом курсе, но вдруг вам интересно как это устрорено внутри. Давайте сломаем объект так чтобы он всегда был равен всему и в том числе None:

class Broken:
    def __eq__(self, obj):
        return True

broken_item = Broken()

print("Проверка тождественность", broken_item is None)
print("Проверка на равенство", broken_item == None)
print("Проверка на сломанность", broken_item == 1)
print("Проверка на сломанность", broken_item == 'Собака')

Результат этого кода будет таким:

Проверка тождественность False
Проверка на равенство True
Проверка на сломанность True
Проверка на сломанность True

Конечно не рекомендуется таким образом ломать поведение объектов. На жаргоне это называется "выстрелить себе в ногу".

Условия в генераторах

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

[<expr(item), ...> for <item, ...> in <sequence> if <condition>]

Или на примере:

>>> [i for i in range(11) if i % 2 == 0]
[0, 2, 4, 6, 8, 10]

Этот пример берет каждый элемент последовательности range(10) и проверяет выполнение условия деления без остатка на 2, если условие выполняется, то элемент попадает в левую часть и в финальный результат. Мы не делаем никаких манипуляций на переменной цикла i, поэтому в результате выполнения этого кода получаем список четных чисел в диапазоне до 10.

Еще один пример проверяет можно ли элемент списка конвертировать в число и возвращает список чисел:

>>> text = "Самые высокие горы мира: Джомолунгма 8848, Чогори 8611, Канченджанга 8586"
>>> sum(int(i) for i in text.replace(',', ' ').split() if i.isnumeric())
26045

Условия одной строкой

Помимо блочного представления оператора if-elif-else у него еще есть краткая однострочная форма:

<выражение1> if <условие> else <выражение2>

Такой формат еще называют тернарным оператором (тот самый который я регулярно проверяю в документации как он работает!). Он позволяет экономить место и делать код болек компактным:

>>> age = 18
>>> print("Ребенок" if age < 18 else "Взрослый")
Взрослый

И конечно вы можете их объединять в цепочку:

age = 14
>>> print("Ребенок" if age < 12 else "Подросток" if age < 18 else "Взрослый")
Подросток

Этот же код можно записать в ввиде блока:

age = 14
if age < 18:
    if age < 12:
        print("Ребенок")
    else:
        print("Подросток")
else:
    print("Взрослый")

Но по моему такая конструкция слишком запутывает.

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

# входящие данные
site = 'Pylot'
url = 'https://pylot.dev/'
tag = None

# обработка
bookmark = {
    'site': site,
    'url': url,
    'tag': tag if tag else 'python'
}

Но если идет проверка на False то можно сократить запись и использовать or:

# входящие данные
site = 'Pylot'
url = 'https://pylot.dev/'
tag = None

# обработка
bookmark = {
    'site': site,
    'url': url,
    'tag': tag or 'python'
}

Если значение tag равно False, то подставится значение по умолчанию.

Ссылки