Skip to content

Глава 7. Условия: статус «зачёт / незачёт» и защита расчётов от пустых данных

Задача главы: научиться выставлять статус по порогу через if / elif / else, отличать «нет данных» от валидного нуля и сокращать простое ветвление до условия в одну строку.


7.1 if / else: статус по порогу

Зачем это нужно. Сравнение из главы Числа и вычисления (test_score >= pass_threshold) само по себе только возвращает True или False. Чтобы по этому результату что-то сделать — выставить статус, напечатать предупреждение, — нужен оператор if: он выполняет один блок кода, если условие истинно, и другой, если ложно.

// PYTHON_EXECUTOR (Pyodide Wasm)
Балл 87: зачёт

Конструкция читается буквально: «если балл больше или равен порогу — статус „зачёт“, иначе — „незачёт“». else необязателен — можно оставить только if, тогда при ложном условии просто ничего не произойдёт.

⚠️ Подводный камень

Первая ловушка — перепутать сравнение с присваиванием. Разница в один символ, а разбор кода Python останавливает совсем: if ждёт логическое выражение, а не команду «присвоить значение».

// PYTHON_EXECUTOR (Pyodide Wasm)
File "report.py", line 4
if test_score = pass_threshold:
^^^^^^^^^^^^^^^^^^^^^^^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?

Вторая ловушка — забытый отступ. Двоеточие после if открывает блок, а Python определяет, что относится к этому блоку, исключительно по отступу в 4 пробела — без него ему негде понять, где начинается тело условия.

// PYTHON_EXECUTOR (Pyodide Wasm)
File "report.py", line 5
print('зачёт')
^
IndentationError: expected an indented block after 'if' statement on line 4
ОшибкаЧто значит для PythonЧеловеческий перевод
SyntaxError«Не понимаю структуру строки»Одинарное = — присваивание, сравнение всегда ==. Python специально не позволяет спутать одно с другим внутри if.
IndentationError«Ожидался блок с отступом после if»Тело условия обозначается отступом в 4 пробела, а не только двоеточием. Без отступа непонятно, что относится к if, а что — уже следующая независимая строка.

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)
Балл 45: незачёт

Пояснение логики: после if <условие>: и после else: обязательно следует блок с отступом — то же самое правило, которое дальше встретится у циклов в главе Циклы. <условие> должно быть выражением, которое возвращает True или False: результатом сравнения (>=, ==, !=), логической операции (and, or, not из главы 2) или, как выяснится в 7.3, любым объектом Python в принципе.


7.2 Каскад elif: буквенная шкала оценки

Зачем это нужно. «Зачёт / незачёт» — это выбор из двух вариантов. Но часто исходов больше двух: перевести балл в буквенную оценку A / B / C / D. elif («else if») достраивает if цепочкой дополнительных проверок, которые выполняются по порядку, пока одна из них не окажется истинной.

// PYTHON_EXECUTOR (Pyodide Wasm)
Балл 82 → оценка B

Важное правило работы каскада: Python проверяет условия сверху вниз и останавливается на первом истинном — 82 >= 90 ложно, 82 >= 75 истинно, оценка B присвоена, а до elif test_score >= pass_threshold дело уже не доходит, хотя формально 82 >= 60 тоже верно.

⚠️ Подводный камень

Именно это правило «первое истинное побеждает» ломает каскад, если условия расставлены в неправильном порядке. Traceback не появится — программа с виду отработает исправно, но результат будет систематически неверным.

// PYTHON_EXECUTOR (Pyodide Wasm)
Балл 95 → оценка D

Балл 95 — отличный результат, но до elif test_score >= 90 код не добрался: широкое условие test_score >= 0 наверху перекрыло собой все более узкие проверки ниже.

ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«Первое истинное условие в каскаде — единственное, что выполнится»Широкое условие (>= 0) стоит раньше узкого (>= 90) — узкое никогда не получает шанса сработать. Traceback не покажет ничего: скрипт «успешно» посчитает всем одну и ту же оценку.

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)
Балл 95 → оценка A

Пояснение логики: условия каскада elif должны идти от самого строгого порога к самому мягкому — сверху вниз, по убыванию требовательности. Тогда более узкая проверка успевает сработать раньше, чем её накроет широкая. Мнемоника: «сначала отсеиваем лучших, в конце остаются все остальные» — else без всякого условия как раз и есть этот финальный сборник «всех остальных».


7.3 Truthy / Falsy и защита от пустых данных

Зачем это нужно. if умеет проверять не только результат сравнения — ему годится любой объект Python. При этом каждый объект интерпретатор относит либо к Truthy («веду себя как истина»), либо к Falsy («веду себя как ложь»). Falsy — это пустая строка '', пустой список [], пустой словарь {}, число 0 и None; всё остальное — Truthy. Благодаря этому проверку на пустоту можно писать короче, чем через len(...) == 0.

// PYTHON_EXECUTOR (Pyodide Wasm)
Нет данных для расчёта

if not group_scores: — идиоматичный способ проверить пустую коллекцию перед тем, как что-то из неё считать; ровно эта защита в следующей главе спасёт от ZeroDivisionError при делении на len(group_scores).

⚠️ Подводный камень

Удобство Truthy/Falsy оборачивается ловушкой, как только Falsy-значение оказывается не «пустотой», а полноценным результатом. Первый случай — балл 0 за реально написанную, но провальную работу:

// PYTHON_EXECUTOR (Pyodide Wasm)
Балл отсутствует

Второй случай — строки. Непустая строка Truthy независимо от того, что в ней написано, поэтому '0' и 'False', пришедшие текстом из выгрузки, ведут себя как истина:

// PYTHON_EXECUTOR (Pyodide Wasm)
Статус подтверждён

Третий случай — не пустое значение, а вовсе отсутствующее поле. .get() из главы Словари без запасного значения возвращает None, и здесь Python уже не промолчит:

// PYTHON_EXECUTOR (Pyodide Wasm)
Traceback (most recent call last):
File "report.py", line 5, in <module>
if test_score >= pass_threshold:
TypeError: '>=' not supported between instances of 'NoneType' and 'int'
ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«0 — Falsy, ветка else истинна»if test_score: не отличает «балла нет» от «балл равен нулю» — оба варианта уходят в else. Для чисел, где 0 — легитимное значение, проверяйте is None, а не Truthy/Falsy.
— (тихая ошибка)«Непустая строка — всегда Truthy»'0' и 'False' содержат текст, похожий на ложь, но это не влияет на Truthy/Falsy: важна только пустота строки. Сравнивайте с конкретным значением явно.
TypeError«>= не работает между NoneType и int»Поле не пришло в выгрузке вовсе, .get() вернул None, и сравнение None >= 60 для Python не определено. Проверяйте is None до сравнения — как и предупреждала глава 5 про .get(key, 0).

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)
Балл принят: 0
Статус подтверждён: False
Балл к расчёту: 0

Пояснение логики: if not container: остаётся отличным способом проверить коллекцию (список, строку, словарь) на пустоту — там Falsy действительно означает «данных нет». Но для одиночного значения, где 0 или '' могут быть законным результатом, нужна явная проверка — is None, сравнение с конкретным значением или запасное значение по умолчанию из .get(key, 0). Тот же принцип объясняет и ловушку из главы Строки и f-строки: .find() при отсутствии совпадения возвращает -1, а -1 — Truthy (не ноль!), поэтому if text.find('x'): сработает, даже когда подстрока не найдена; правильно — сравнивать результат явно: if text.find('x') != -1:.


7.4 Условие в одну строку

Зачем это нужно. Когда весь смысл if / else — присвоить одной переменной одно из двух значений, четыре строки кода избыточны. Тернарный оператор сворачивает такую конструкцию в одну строку — ровно тот приём, который мельком уже мелькал в главе Числа и вычисления в виде (1 if leftover_students else 0).

// PYTHON_EXECUTOR (Pyodide Wasm)
Балл 87: зачёт

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

⚠️ Подводный камень

Соблазн — вложить тернарники друг в друга, чтобы получить каскад из 7.2 в одну строку. Технически это работает, но ловушка с порядком условий из 7.2 никуда не девается — она просто становится куда незаметнее для глаза.

// PYTHON_EXECUTOR (Pyodide Wasm)
D

Та же ошибка порядка, что и в 7.2 (широкое условие >= 0 стоит первым), — но здесь её почти невозможно найти взглядом: пять условий и четыре результата свалены в одну строку без единого отступа, который в elif-каскаде наглядно показывал бы иерархию проверок.

ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«Вложенные тернарники читаются и вычисляются в том же порядке, что и elif»Ошибка порядка условий из 7.2 сохраняется и в тернарнике, но без отступов её негде заметить при чтении кода. Чем больше исходов — тем выше цена нечитаемости.

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)
A

Пояснение логики: тернарный оператор — инструмент ровно для одного выбора из двух вариантов, который к тому же не требует отдельного блока кода (годится прямо внутри print() или f-строки). Как только исходов становится три и больше, вложенные тернарники начинают экономить строки кода за счёт читаемости и надёжности — на этом месте однострочное условие уступает место обычному каскаду elif.


7.5 Мини-словарик ошибок седьмой главы

ОшибкаЧто значит для PythonЧеловеческий перевод
SyntaxError«Не понимаю структуру строки»if test_score = pass_threshold: — одинарное = вместо ==. Python подсказывает прямо в тексте ошибки, что имелось в виду.
IndentationError«Ожидался блок с отступом после if»Тело if / elif / else не обозначено отступом в 4 пробела.
TypeError«>= не работает между NoneType и int»Поле не пришло в выгрузке, .get() без запасного значения вернул None, а сравнение None >= 60 для Python не определено.
— (тихая ошибка)«Первое истинное условие в каскаде побеждает»Широкое условие стоит выше узкого в elif — узкое никогда не получает шанса выполниться, оценка считается неверно без единого traceback.
— (тихая ошибка)«0 — Falsy»if test_score: не отличает «балла нет» от «балл 0» — валидный ноль тихо уходит в ветку else.
— (тихая ошибка)«Непустая строка — всегда Truthy»'0' и 'False' текстом воспринимаются как истина независимо от смысла написанного внутри.
— (тихая ошибка)«Вложенный тернарник вычисляется в том же порядке, что и elif»Ошибка порядка условий из 7.2 повторяется в однострочной записи, но без отступов её труднее заметить при чтении.

Мини-кейс: простановка статусов по журналу с пропусками

Задача: по журналу группы, где часть баллов ещё не выставлена, а один из учеников получил валидный ноль, проставить каждому статус «зачёт / незачёт / нет данных» и подвести итог по группе.

// PYTHON_EXECUTOR (Pyodide Wasm)
--- СТАТУСЫ ГРУППЫ ---
Иванов Иван: зачёт
Смирнова Ольга: незачёт
Ким Юрий: нет данных
Петров Пётр: незачёт
Сидорова Анна: зачёт
--- ИТОГО ---
Зачёт: 2 | Незачёт: 2 | Нет данных: 1

Что получилось: проверка if not group_journal: стоит первой линией защиты — на пустом журнале скрипт не пытается ничего считать. Внутри цикла отсутствие балла у Кима Юрия и валидный ноль у Петрова Петра различены явной проверкой test_score is None, а не сокращённой if test_score: — если бы использовалась вторая, ноль Петрова ошибочно попал бы в ту же группу «нет данных», что и по-настоящему невыставленный балл Кима. Статус «зачёт / незачёт» для всех остальных строк собран тернарным оператором из 7.4, а три счётчика (passed_count, failed_count, missing_count) дают честную сводку по группе без единой строки, потерянной в пропусках.

⚠️ Обход журнала циклом for здесь взят чуть забегая вперёд — сам инструмент разбирается в следующей главе Циклы; накопители-счётчики (passed_count += 1) тоже получат отдельное имя и разбор там же.


Чек-лист главы

  • Пишу условие сравнения через ==, а не =, и ставлю двоеточие с отступом в 4 пробела после if / elif / else
  • Строю каскад elif по убыванию строгости — от узкого порога к широкому, а не наоборот
  • Проверяю пустую коллекцию через if not container:, а не через ручное сравнение длины с нулём
  • Не проверяю потенциально нулевое значение как if score: — использую is None, чтобы отличить «нет данных» от валидного нуля
  • Помню, что непустые строки '0' и 'False' — Truthy, и сравниваю их с конкретным значением, а не полагаюсь на Truthy/Falsy
  • Проверяю поле на None (или беру .get(key, 0) из главы 5) перед сравнением с числом, чтобы не поймать TypeError
  • Использую условие в одну строку только для простого выбора из двух вариантов
  • Не вкладываю тернарные операторы друг в друга — при трёх и более исходах перехожу на elif
  • Узнаю SyntaxError: invalid syntax. Maybe you meant '==' ... как сигнал перепутанного присваивания
  • Узнаю IndentationError: expected an indented block как сигнал забытого отступа после if / elif / else

Дальше: Циклы