Условные конструкции: if, elif, else, тернарный и walrus

После работы с типами переходим к управлению потоком исполнения. В Python условные конструкции компактны и читаемы - без скобок вокруг условия и фигурных вокруг блока. В этом уроке разберём базовый if/elif/else, тернарный оператор для коротких условий и относительно новый оператор моржа :=.

Базовый if

age = 25

if age >= 18:
    print("Совершеннолетний")

Блок отделяется отступом (4 пробела по PEP 8). Никаких фигурных скобок. Двоеточие после условия обязательно.

С веткой else:

if age >= 18:
    print("Совершеннолетний")
else:
    print("Несовершеннолетний")

И с цепочкой elif:

if age < 13:
    category = "ребёнок"
elif age < 18:
    category = "подросток"
elif age < 65:
    category = "взрослый"
else:
    category = "пенсионер"

elif это сокращение от else if. В отличие от Go, где есть else if, в Python вложенных конструкций нет - всё через одно ключевое слово.

Долгое время в Python не было аналога `switch`. Использовали либо цепочки elif, либо dict-маппинг. С Python 3.10 появился `match-case` (structural pattern matching) - но это не просто switch, а более мощный механизм. О нём в [уроке 13](./13-match-case.md).

Условие = любое выражение

В условии может быть что угодно, что приводится к bool:

if items:                # коллекция не пустая
    process(items)

if user.is_active:       # атрибут truthy
    send_email(user)

if not config.get("debug"):  # ключ отсутствует или falsy
    enable_optimizations()

if 0 < age < 150:        # цепочка сравнений
    save(age)

Последний пример - цепочка сравнений (chained comparison). Python поддерживает математическую нотацию a < x < b как сокращение для a < x and x < b, но с лучшей читаемостью и одним вычислением x.

Тернарный оператор

Для коротких условий есть однострочная форма:

status = "взрослый" if age >= 18 else "ребёнок"

Структура: <value_if_true> if <condition> else <value_if_false>.

Это выражение, не оператор - возвращает значение. Можно использовать в выражениях:

print(f"Доступ: {'разрешён' if user.authorized else 'запрещён'}")

discount = 0.1 if total > 1000 else 0
Тернарный можно вкладывать, но это быстро становится нечитаемым:
# Плохо
label = "A" if x > 90 else "B" if x > 80 else "C" if x > 70 else "F"

# Лучше
if x > 90:
    label = "A"
elif x > 80:
    label = "B"
elif x > 70:
    label = "C"
else:
    label = "F"

Правило: один тернарный - норма, два - граница, три - явно перебор.

Оператор моржа :=

С Python 3.8 появился оператор присваивания в выражении - walrus operator (получил имя из-за похожести на морду моржа):

# Без walrus - вычисляем дважды
if len(items) > 10:
    print(f"Слишком много: {len(items)}")

# С walrus - одно вычисление
if (n := len(items)) > 10:
    print(f"Слишком много: {n}")

Это полезно когда:

  1. Значение нужно для условия и для тела
  2. Вычисление дорогое
  3. Хочется избежать промежуточной переменной перед условием

Ещё типичный пример - чтение файла построчно с проверкой:

while (line := f.readline()):
    process(line)

В этом коде readline() вызывается один раз за итерацию, его результат проверяется (пустая строка = конец файла) и используется в теле.

Walrus в простых местах часто **ухудшает** читаемость. Используй его когда явно избавляешься от двойного вычисления или продлеваешь жизнь переменной за пределы выражения. Для остального обычное `=` понятнее.

pass - пустой блок

В Python нельзя оставить блок пустым - получишь синтаксическую ошибку. Если нужна заглушка:

if config.debug:
    pass  # TODO: добавить отладочный вывод
else:
    run_production_mode()

pass - официальная заглушка «ничего не делать». Не путать с ... (Ellipsis), который тоже работает как заглушка (часто в type stubs):

def not_implemented() -> int:
    ...  # placeholder для будущей реализации

pass и ... эквивалентны как заглушки - pass чаще для блоков, ... для тел функций (соглашение типизаторов).

Распространённые ошибки

1. Сравнение с константой не в том порядке.

В C принято писать if (NULL == ptr) чтобы поймать опечатку = вместо ==. В Python это не нужно - присваивание = нельзя поставить в условие (if x = 5: - SyntaxError). Walrus := это отдельный оператор.

2. Использование is вместо == для значений.

# Плохо - может работать случайно из-за интернирования
if x is 100:
    ...

# Правильно
if x == 100:
    ...

Использовать is только для None, True, False.

3. Перекрытие встроенных имён.

# Плохо - переопределяет встроенную функцию
list = [1, 2, 3]
if list:
    ...

Используй другие имена: items, numbers, users. Линтер ругается на shadowing builtins.

Идиомы

Проверка на null с дефолтом:

name = user.name if user else "Anonymous"
# или короче
name = (user and user.name) or "Anonymous"

Маппинг через dict вместо elif:

# Длинный if/elif
def get_label(grade):
    if grade == "A":
        return "Отлично"
    elif grade == "B":
        return "Хорошо"
    elif grade == "C":
        return "Удовлетворительно"
    else:
        return "Неизвестно"

# Через dict
def get_label(grade):
    labels = {"A": "Отлично", "B": "Хорошо", "C": "Удовлетворительно"}
    return labels.get(grade, "Неизвестно")

Второй вариант декларативный и легче расширять - подробно про такой dispatch в уроке про словари.

Early return вместо вложенности:

# Плохо - глубокая вложенность
def process(user):
    if user is not None:
        if user.is_active:
            if user.has_permission("write"):
                do_work(user)

# Хорошо - guards в начале
def process(user):
    if user is None:
        return
    if not user.is_active:
        return
    if not user.has_permission("write"):
        return
    do_work(user)

Это паттерн «guard clauses» - выходим из функции рано, чтобы основная логика была плоской. Тому же приёму посвящён урок «Ранние return и меньше вложенности».

Сравнение с другими языками

КонструкцияPythonGoPHP
ifif x > 0:if x > 0 {if ($x > 0)
else ifelifelse ifelseif
тернарныйa if cond else bнет, через if/elsecond ? a : b
switchmatch-case (3.10+)switchswitch/match
chained compare0 < x < 10через ANDчерез &&
walrus:= (3.8+):= (declaration)нет

Python и Go используют :=, но в разных значениях: в Go - объявление переменной с выводом типа, в Python - присваивание внутри выражения.

Мини-задание

  1. Перепиши вложенный if с early return:
def can_buy(user, product):
    if user.is_registered:
        if user.balance >= product.price:
            if product.in_stock:
                return True
    return False
  1. Используй тернарный оператор:
def describe_age(age):
    if age < 18:
        return "ребёнок"
    else:
        return "взрослый"
# Перепиши в одну строку через тернарный
  1. Применяй walrus в чтении ввода:
# Прочитать ввод пока не введут пустую строку
while True:
    line = input("> ")
    if not line:
        break
    print(f"Получили: {line}")

# Перепиши через walrus в одну условную проверку

Что дальше

Освоили условия. В следующем уроке - циклы for и while: range/enumerate/zip, break/continue, идиома for-else, типичные ошибки.

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.