Глава 12. Стандартная библиотека: `statistics`, `datetime`, `collections`
Задача главы: перестать писать вручную то, что в Python уже есть готовым и проверенным — честную статистику по группе, разбор и сравнение дат, подсчёт распределения статусов.
12.1 statistics: mean(), median(), stdev() — когда медиана честнее среднего
Зачем это нужно. Средний балл считался вручную через sum() / len() ещё в главе Числа и вычисления, а потом переехал в собственную функцию calculate_average() в главе Функции. У среднего есть слабое место: один резкий выброс — отчисленный с нулём или, наоборот, случайный стопроцентник — тянет среднее в свою сторону и искажает картину по всей группе. Модуль statistics из стандартной библиотеки Python уже содержит проверенные функции для медианы (значения «посередине», устойчивого к выбросам) и разброса — не нужно ни писать сортировку руками, ни держать в голове разницу между чётной и нечётной длиной списка.
Средний балл: 68.1Медиана: 78.0Разброс (стандартное отклонение): 28.7Разница заметна сразу: средний балл — 68.1, а медиана — 78.0. Шесть из семи баллов в группе выше 68, но единственная пятнадцатка тянет среднее вниз и создаёт впечатление слабой группы. Медиана в этом смысле честнее — на неё влияет положение значения в ряду, а не его абсолютная величина.
⚠️ Подводный камень
Все три функции ожидают хотя бы одно значение. Пустой список баллов — ученик, который не сдал ни одного модуля, тот же сценарий, что уже ронял calculate_average() с ZeroDivisionError в главах 9 и 11, — здесь роняет statistics.mean() с другим именем ошибки.
Traceback (most recent call last): File "report.py", line 4, in <module> average_score = statistics.mean(empty_scores)statistics.StatisticsError: mean requires at least one data point| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
StatisticsError | «Функции statistics нужна хотя бы одна точка данных» | Пустой список баллов — та же ситуация, что раньше давала ZeroDivisionError в собственной calculate_average(). Защита та же: проверять len(scores) > 0 до вызова, как уже делали в try / except главы Ошибки и исключения. |
Правильный паттерн
NoneПояснение логики: проверка if not scores отсекает пустой список до того, как он попадёт в statistics.median(), — ровно тот же приём, что защищал calculate_average() в предыдущих главах, просто применённый к новому инструменту.
12.2 datetime: разбор дат сдачи, strptime() / strftime(), разница дат
Зачем это нужно. Дата сдачи в выгрузке — это текст ('16.05.2024'), а не число: сравнить такую строку со сроком «раньше/позже» напрямую нельзя. datetime.strptime() разбирает строку в объект даты по заданному формату, strftime() (или спецификатор : в f-строке) форматирует дату обратно в строку, а разность двух объектов datetime сразу даёт число дней между ними.
Просрочка: 3 дн.%d.%m.%Y — код формата: день, месяц, год через точки. Формат в strptime() обязан совпадать с реальной строкой посимвольно — это не подсказка Python, а точное описание, как читать текст.
⚠️ Подводный камень
Первая ловушка — формат в strptime() не совпадает со строкой. Это падает сразу и явно.
Traceback (most recent call last): File "report.py", line 3, in <module> submitted_at = datetime.strptime('18.05.2024', '%Y-%m-%d')ValueError: time data '18.05.2024' does not match format '%Y-%m-%d'Вторая ловушка тише и опаснее: дата вида '01.02.2024' синтаксически подходит под два разных формата сразу — и strptime() разберёт её без единой жалобы в обоих случаях, просто выдав два разных календарных дня.
2024-01-02 00:00:002024-02-01 00:00:00Traceback не появится ни в одном из вариантов — обе строки для Python одинаково валидны, различается только предположение о порядке дня и месяца.
| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
ValueError (strptime) | «Строка не раскладывается по этому формату» | Формат, переданный вторым аргументом, не совпадает с реальным видом строки (перепутаны разделители, порядок или число знаков). Формат нужно сверять со строкой посимвольно. |
| — (тихая ошибка) | «strptime() разбирает строку по ЛЮБОМУ формату, который синтаксически подходит» | '01.02.2024' подходит и под «месяц.день.год», и под «день.месяц.год» — Python не может угадать, какой порядок задумал источник данных. Без сверки с документацией выгрузки один и тот же текст тихо превращается в два разных дня. |
Правильный паттерн
Дата сдачи: 01.02.2024Пояснение логики: неоднозначность решают один раз, до чтения любых данных — сверкой с документацией источника выгрузки, а не угадыванием по каждой отдельной строке. Формат стоит держать в одной константе (DATE_FORMAT) и использовать её и при чтении (strptime), и при выводе (спецификатор : в f-строке, который работает как strftime()) — тогда формат меняется в одном месте, а не расползается копиями по коду.
12.3 collections.Counter: распределение статусов и most_common()
Зачем это нужно. Посчитать, сколько учеников получили «зачёт», а сколько «незачёт», можно вручную через словарь-счётчик и setdefault(), как в главе Словари. Counter из модуля collections делает то же самое в одну строку — принимает любую последовательность и сразу возвращает словарь-подобный объект с количеством каждого значения, плюс метод most_common() для готового рейтинга.
Counter({'зачёт': 4, 'незачёт': 2})[('зачёт', 4)]most_common() без аргумента вернёт весь список от самого частого к самому редкому; most_common(1) — только первую позицию рейтинга.
⚠️ Подводный камень
Counter принимает любую последовательность и считает её элементы — а элементы строки — это отдельные символы, а не слова. Забытый .split() превращает подсчёт статусов в подсчёт букв.
Counter({'ч': 3, 'а': 3, 'е': 3, 'т': 3, ' ': 2, 'з': 2, 'н': 1, 'ы': 1, 'й': 1})Traceback не появится — код рабочий, Counter честно посчитал именно то, что ему передали: не три статуса, а набор отдельных символов.
| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
| — (тихая ошибка) | «Counter() считает элементы переданной последовательности» | Строка — тоже последовательность, но её элементы — отдельные символы. Без .split() подсчёт статусов молча превращается в подсчёт букв — результат выглядит как рабочий словарь, но считает не то. |
Правильный паттерн
Counter({'зачёт': 2, 'незачёт': 1})Пояснение логики: .split() сначала превращает строку в список отдельных слов (приём из главы Строки и f-строки), и только затем Counter считает элементы — уже правильные, целые статусы, а не отдельные буквы.
12.4 Мини-словарик ошибок двенадцатой главы
| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
StatisticsError | «Функции statistics нужна хотя бы одна точка данных» | Пустой список баллов — тот же сценарий, что раньше ронял calculate_average() с ZeroDivisionError. Проверять len(scores) > 0 до вызова. |
ValueError (strptime) | «Строка не раскладывается по переданному формату» | Формат-строка не совпадает с реальным видом даты в выгрузке — разделители, порядок или число знаков перепутаны. |
| — (тихая ошибка) | «strptime() разбирает строку по любому синтаксически подходящему формату» | '01.02.2024' без ошибок читается и как 2 января, и как 1 февраля — Python не угадывает порядок дня и месяца, это нужно знать заранее из документации источника. |
| — (тихая ошибка) | «Counter() считает элементы переданной последовательности» | Строка без .split() считается по символам, а не по словам или статусам — результат похож на рабочий, но неверен по смыслу. |
Мини-кейс: сводка по потоку — медиана, разброс, топ статусов, просрочки по датам
Задача: по группе учеников с их баллами и датами сдачи посчитать статус и просрочку каждого, а затем свести всё в общую картину по потоку: медиану и разброс среднего балла, распределение статусов и число просрочивших дедлайн.
--- СВОДКА ПО ПОТОКУ ---Иванов Иван: 71.7 — зачёт, просрочка 1 дн.Смирнова Ольга: 91.7 — зачётКим Юрий: 48.3 — незачёт, просрочка 5 дн.Петров Пётр: 10.0 — незачёт, просрочка 4 дн.Сидорова Анна: 87.7 — зачёт--- Медиана среднего балла: 71.7 ------ Разброс (стандартное отклонение): 33.6 ------ Самый частый статус: зачёт (3 из 5) ------ Просрочили дедлайн: 3 из 5 ---Что получилось: три модуля стандартной библиотеки закрыли ровно те задачи, для которых раньше пришлось бы писать код вручную — statistics дал честную медиану вместо искажённого выбросами среднего, datetime превратил текстовые даты в сравнимые числа дней просрочки, а Counter за одну строку собрал распределение статусов с готовым рейтингом. Ни разбор дат, ни подсчёт статусов, ни расчёт медианы не потребовали новых циклов или условий — только новые инструменты над уже знакомыми списками и словарями.
⚠️ Всё, что построено в этой и предыдущих главах — чтение выгрузки, защита от кривых строк, свои функции, инструменты стандартной библиотеки, — до сих пор жило в отдельных примерах по одной задаче на главу. Собрать всё это в один сквозной скрипт «выгрузка → расчёты → отчёт» — тема последней главы, Итоговый проект Отчёт по успеваемости.
Чек-лист главы
- Считаю медиану через
statistics.median(), когда в данных возможны выбросы, искажающие среднее - Знаю, что
statistics.mean()/median()/stdev()роняютStatisticsErrorна пустом списке, и проверяюlen(scores) > 0заранее - Разбираю даты из выгрузки через
datetime.strptime()с форматом, а не сравниваю строки как текст - Держу формат даты в одной константе (
DATE_FORMAT) и использую её и при разборе, и при выводе - Сверяю формат даты с документацией источника выгрузки, а не угадываю порядок дня и месяца по внешнему виду строки
- Считаю разницу дат через вычитание
datetimeи атрибут.daysу результата - Считаю распределение категорий через
Counter()вместо ручного словаря-счётчика сsetdefault() - Передаю в
Counter()список слов (.split()), а не строку целиком, если считаю не буквы, а слова или статусы - Достаю лидера распределения через
most_common(1), а не сортирую словарь вручную