Глава 7. Условия: статус «зачёт / незачёт» и защита расчётов от пустых данных
Задача главы: научиться выставлять статус по порогу через
if / elif / else, отличать «нет данных» от валидного нуля и сокращать простое ветвление до условия в одну строку.
7.1 if / else: статус по порогу
Зачем это нужно. Сравнение из главы Числа и вычисления (test_score >= pass_threshold) само по себе только возвращает True или False. Чтобы по этому результату что-то сделать — выставить статус, напечатать предупреждение, — нужен оператор if: он выполняет один блок кода, если условие истинно, и другой, если ложно.
Балл 87: зачётКонструкция читается буквально: «если балл больше или равен порогу — статус „зачёт“, иначе — „незачёт“». else необязателен — можно оставить только if, тогда при ложном условии просто ничего не произойдёт.
⚠️ Подводный камень
Первая ловушка — перепутать сравнение с присваиванием. Разница в один символ, а разбор кода Python останавливает совсем: if ждёт логическое выражение, а не команду «присвоить значение».
File "report.py", line 4 if test_score = pass_threshold: ^^^^^^^^^^^^^^^^^^^^^^^^^^^SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?Вторая ловушка — забытый отступ. Двоеточие после if открывает блок, а Python определяет, что относится к этому блоку, исключительно по отступу в 4 пробела — без него ему негде понять, где начинается тело условия.
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, а что — уже следующая независимая строка. |
Правильный паттерн
Балл 45: незачётПояснение логики: после if <условие>: и после else: обязательно следует блок с отступом — то же самое правило, которое дальше встретится у циклов в главе Циклы. <условие> должно быть выражением, которое возвращает True или False: результатом сравнения (>=, ==, !=), логической операции (and, or, not из главы 2) или, как выяснится в 7.3, любым объектом Python в принципе.
7.2 Каскад elif: буквенная шкала оценки
Зачем это нужно. «Зачёт / незачёт» — это выбор из двух вариантов. Но часто исходов больше двух: перевести балл в буквенную оценку A / B / C / D. elif («else if») достраивает if цепочкой дополнительных проверок, которые выполняются по порядку, пока одна из них не окажется истинной.
Балл 82 → оценка BВажное правило работы каскада: Python проверяет условия сверху вниз и останавливается на первом истинном — 82 >= 90 ложно, 82 >= 75 истинно, оценка B присвоена, а до elif test_score >= pass_threshold дело уже не доходит, хотя формально 82 >= 60 тоже верно.
⚠️ Подводный камень
Именно это правило «первое истинное побеждает» ломает каскад, если условия расставлены в неправильном порядке. Traceback не появится — программа с виду отработает исправно, но результат будет систематически неверным.
Балл 95 → оценка DБалл 95 — отличный результат, но до elif test_score >= 90 код не добрался: широкое условие test_score >= 0 наверху перекрыло собой все более узкие проверки ниже.
| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
| — (тихая ошибка) | «Первое истинное условие в каскаде — единственное, что выполнится» | Широкое условие (>= 0) стоит раньше узкого (>= 90) — узкое никогда не получает шанса сработать. Traceback не покажет ничего: скрипт «успешно» посчитает всем одну и ту же оценку. |
Правильный паттерн
Балл 95 → оценка AПояснение логики: условия каскада elif должны идти от самого строгого порога к самому мягкому — сверху вниз, по убыванию требовательности. Тогда более узкая проверка успевает сработать раньше, чем её накроет широкая. Мнемоника: «сначала отсеиваем лучших, в конце остаются все остальные» — else без всякого условия как раз и есть этот финальный сборник «всех остальных».
7.3 Truthy / Falsy и защита от пустых данных
Зачем это нужно. if умеет проверять не только результат сравнения — ему годится любой объект Python. При этом каждый объект интерпретатор относит либо к Truthy («веду себя как истина»), либо к Falsy («веду себя как ложь»). Falsy — это пустая строка '', пустой список [], пустой словарь {}, число 0 и None; всё остальное — Truthy. Благодаря этому проверку на пустоту можно писать короче, чем через len(...) == 0.
Нет данных для расчётаif not group_scores: — идиоматичный способ проверить пустую коллекцию перед тем, как что-то из неё считать; ровно эта защита в следующей главе спасёт от ZeroDivisionError при делении на len(group_scores).
⚠️ Подводный камень
Удобство Truthy/Falsy оборачивается ловушкой, как только Falsy-значение оказывается не «пустотой», а полноценным результатом. Первый случай — балл 0 за реально написанную, но провальную работу:
Балл отсутствуетВторой случай — строки. Непустая строка Truthy независимо от того, что в ней написано, поэтому '0' и 'False', пришедшие текстом из выгрузки, ведут себя как истина:
Статус подтверждёнТретий случай — не пустое значение, а вовсе отсутствующее поле. .get() из главы Словари без запасного значения возвращает None, и здесь Python уже не промолчит:
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). |
Правильный паттерн
Балл принят: 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).
Балл 87: зачётЭта же строка встретится в главе Циклы внутри for, чтобы одной строкой выставлять статус каждому баллу группы — тернарный оператор особенно удобен там, где присваивание нужно повторить много раз подряд.
⚠️ Подводный камень
Соблазн — вложить тернарники друг в друга, чтобы получить каскад из 7.2 в одну строку. Технически это работает, но ловушка с порядком условий из 7.2 никуда не девается — она просто становится куда незаметнее для глаза.
DТа же ошибка порядка, что и в 7.2 (широкое условие >= 0 стоит первым), — но здесь её почти невозможно найти взглядом: пять условий и четыре результата свалены в одну строку без единого отступа, который в elif-каскаде наглядно показывал бы иерархию проверок.
| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
| — (тихая ошибка) | «Вложенные тернарники читаются и вычисляются в том же порядке, что и elif» | Ошибка порядка условий из 7.2 сохраняется и в тернарнике, но без отступов её негде заметить при чтении кода. Чем больше исходов — тем выше цена нечитаемости. |
Правильный паттерн
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 повторяется в однострочной записи, но без отступов её труднее заметить при чтении. |
Мини-кейс: простановка статусов по журналу с пропусками
Задача: по журналу группы, где часть баллов ещё не выставлена, а один из учеников получил валидный ноль, проставить каждому статус «зачёт / незачёт / нет данных» и подвести итог по группе.
--- СТАТУСЫ ГРУППЫ ---Иванов Иван: зачётСмирнова Ольга: незачётКим Юрий: нет данныхПетров Пётр: незачётСидорова Анна: зачёт--- ИТОГО ---Зачёт: 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
Дальше: Циклы