Skip to content

Глава 12. Стандартная библиотека: `statistics`, `datetime`, `collections`

Задача главы: перестать писать вручную то, что в Python уже есть готовым и проверенным — честную статистику по группе, разбор и сравнение дат, подсчёт распределения статусов.


12.1 statistics: mean(), median(), stdev() — когда медиана честнее среднего

Зачем это нужно. Средний балл считался вручную через sum() / len() ещё в главе Числа и вычисления, а потом переехал в собственную функцию calculate_average() в главе Функции. У среднего есть слабое место: один резкий выброс — отчисленный с нулём или, наоборот, случайный стопроцентник — тянет среднее в свою сторону и искажает картину по всей группе. Модуль statistics из стандартной библиотеки Python уже содержит проверенные функции для медианы (значения «посередине», устойчивого к выбросам) и разброса — не нужно ни писать сортировку руками, ни держать в голове разницу между чётной и нечётной длиной списка.

// PYTHON_EXECUTOR (Pyodide Wasm)
Средний балл: 68.1
Медиана: 78.0
Разброс (стандартное отклонение): 28.7

Разница заметна сразу: средний балл — 68.1, а медиана — 78.0. Шесть из семи баллов в группе выше 68, но единственная пятнадцатка тянет среднее вниз и создаёт впечатление слабой группы. Медиана в этом смысле честнее — на неё влияет положение значения в ряду, а не его абсолютная величина.

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

Все три функции ожидают хотя бы одно значение. Пустой список баллов — ученик, который не сдал ни одного модуля, тот же сценарий, что уже ронял calculate_average() с ZeroDivisionError в главах 9 и 11, — здесь роняет statistics.mean() с другим именем ошибки.

// PYTHON_EXECUTOR (Pyodide Wasm)
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 главы Ошибки и исключения.

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

// PYTHON_EXECUTOR (Pyodide Wasm)
None

Пояснение логики: проверка if not scores отсекает пустой список до того, как он попадёт в statistics.median(), — ровно тот же приём, что защищал calculate_average() в предыдущих главах, просто применённый к новому инструменту.


12.2 datetime: разбор дат сдачи, strptime() / strftime(), разница дат

Зачем это нужно. Дата сдачи в выгрузке — это текст ('16.05.2024'), а не число: сравнить такую строку со сроком «раньше/позже» напрямую нельзя. datetime.strptime() разбирает строку в объект даты по заданному формату, strftime() (или спецификатор : в f-строке) форматирует дату обратно в строку, а разность двух объектов datetime сразу даёт число дней между ними.

// PYTHON_EXECUTOR (Pyodide Wasm)
Просрочка: 3 дн.

%d.%m.%Y — код формата: день, месяц, год через точки. Формат в strptime() обязан совпадать с реальной строкой посимвольно — это не подсказка Python, а точное описание, как читать текст.

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

Первая ловушка — формат в strptime() не совпадает со строкой. Это падает сразу и явно.

// PYTHON_EXECUTOR (Pyodide Wasm)
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() разберёт её без единой жалобы в обоих случаях, просто выдав два разных календарных дня.

// PYTHON_EXECUTOR (Pyodide Wasm)
2024-01-02 00:00:00
2024-02-01 00:00:00

Traceback не появится ни в одном из вариантов — обе строки для Python одинаково валидны, различается только предположение о порядке дня и месяца.

ОшибкаЧто значит для PythonЧеловеческий перевод
ValueError (strptime)«Строка не раскладывается по этому формату»Формат, переданный вторым аргументом, не совпадает с реальным видом строки (перепутаны разделители, порядок или число знаков). Формат нужно сверять со строкой посимвольно.
— (тихая ошибка)«strptime() разбирает строку по ЛЮБОМУ формату, который синтаксически подходит»'01.02.2024' подходит и под «месяц.день.год», и под «день.месяц.год» — Python не может угадать, какой порядок задумал источник данных. Без сверки с документацией выгрузки один и тот же текст тихо превращается в два разных дня.

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

// PYTHON_EXECUTOR (Pyodide Wasm)
Дата сдачи: 01.02.2024

Пояснение логики: неоднозначность решают один раз, до чтения любых данных — сверкой с документацией источника выгрузки, а не угадыванием по каждой отдельной строке. Формат стоит держать в одной константе (DATE_FORMAT) и использовать её и при чтении (strptime), и при выводе (спецификатор : в f-строке, который работает как strftime()) — тогда формат меняется в одном месте, а не расползается копиями по коду.


12.3 collections.Counter: распределение статусов и most_common()

Зачем это нужно. Посчитать, сколько учеников получили «зачёт», а сколько «незачёт», можно вручную через словарь-счётчик и setdefault(), как в главе Словари. Counter из модуля collections делает то же самое в одну строку — принимает любую последовательность и сразу возвращает словарь-подобный объект с количеством каждого значения, плюс метод most_common() для готового рейтинга.

// PYTHON_EXECUTOR (Pyodide Wasm)
Counter({'зачёт': 4, 'незачёт': 2})
[('зачёт', 4)]

most_common() без аргумента вернёт весь список от самого частого к самому редкому; most_common(1) — только первую позицию рейтинга.

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

Counter принимает любую последовательность и считает её элементы — а элементы строки — это отдельные символы, а не слова. Забытый .split() превращает подсчёт статусов в подсчёт букв.

// PYTHON_EXECUTOR (Pyodide Wasm)
Counter({'ч': 3, 'а': 3, 'е': 3, 'т': 3, ' ': 2, 'з': 2, 'н': 1, 'ы': 1, 'й': 1})

Traceback не появится — код рабочий, Counter честно посчитал именно то, что ему передали: не три статуса, а набор отдельных символов.

ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«Counter() считает элементы переданной последовательности»Строка — тоже последовательность, но её элементы — отдельные символы. Без .split() подсчёт статусов молча превращается в подсчёт букв — результат выглядит как рабочий словарь, но считает не то.

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

// PYTHON_EXECUTOR (Pyodide Wasm)
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() считается по символам, а не по словам или статусам — результат похож на рабочий, но неверен по смыслу.

Мини-кейс: сводка по потоку — медиана, разброс, топ статусов, просрочки по датам

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

// PYTHON_EXECUTOR (Pyodide Wasm)
--- СВОДКА ПО ПОТОКУ ---
Иванов Иван: 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), а не сортирую словарь вручную

Дальше: Итоговый проект Отчёт по успеваемости