Материал

Практика Python: код, объекты и конкурентность

Здесь самостоятельные задания с учебными данными. Сначала сделайте свою попытку, затем проверьте крайние случаи и откройте подсказку. Время — ориентир; для проекта учитывайте отдельное время на тесты и оформление.

Как выбрать практику · Карта промахов

Прокрутите таблицу по горизонтали →

Задание Этап Ориентир
Настройки двух посетителей Базовый Python 25 мин
Повторы в ленте заказов Базовый Python 25 мин
Самый дорогой заказ без скрытых потерь Базовый Python 20 мин
План посадки без общих строк Базовый Python 25 мин
Поиск блюда по короткому коду Базовый Python 25 мин
Чистый комментарий для кухни Базовый Python 25 мин
Сжатие сигналов табло Базовый Python 30 мин
Обратный справочник аллергенов Базовый Python 25 мин
Позиция меню в множестве Инженерный Python 30 мин
Наборы контейнеров для заказа Инженерный Python 40 мин
Ромб наследования в обработчике Инженерный Python 30 мин
Регистрация типов уведомлений Инженерный Python 35 мин
Каждый callback отправляет свой статус Инженерный Python 25 мин
Дежурства по кругу Базовый Python 25 мин
Потоковый отчёт по позициям чека Инженерный Python 35 мин
Декоратор аудита результата Инженерный Python 30 мин
Отчёт публикуется только целиком Инженерный Python 40 мин
Асинхронный сбор цен поставщиков Async и фоновые задачи 50 мин
Контрольная сумма талона Базовый Python 25 мин
Число вариантов дегустационного набора Инженерный Python 35 мин

Настройки двух посетителей

Кафе хранит шаблон настроек: {"allergens": ["nuts"], "alerts": {"email": True}}. Напишите new_preferences(template). Создайте настройки для двух посетителей; первому добавьте milk и выключите email. У второго и у шаблона должны остаться исходные значения. Затем разберите функцию с аргументом allergens=[]: почему два вызова могут получить общий список? Покажите разницу между == и is на своих объектах.

Проверка результата

  • Вложенные списки и словари независимы; тест проверяет каждый уровень.
  • Пустой список, повторный вызов и список внутри tuple разобраны отдельно.
  • Объяснено, что копирование внешнего словаря не копирует вложенные объекты.
Подсказка — после своей попытки

Нарисуйте, какие переменные указывают на один объект.

Усложнение: Добавьте необязательные настройки, не используя изменяемый аргумент по умолчанию.

Вернуться к этапу

Повторы в ленте заказов

Сканер прислал идентификаторы [0, 14, 14, 7, 0, 19, 7]. Напишите unique_order_ids(ids): сохраните порядок первого появления и верните [0, 14, 7, 19]. Ноль — допустимый идентификатор. Входной список менять нельзя. Объясните время и дополнительную память на большом потоке.

Проверка результата

  • Пустой вход даёт пустой результат.
  • Повторы и 0 обработаны без сортировки и проверки истинности вместо наличия.
  • Измерьте рост времени на 10 000 и 100 000 элементов.
Подсказка — после своей попытки

Разделите порядок результата и проверку того, видели ли уже элемент.

Усложнение: Принимайте любой конечный iterable, включая одноразовый генератор.

Вернуться к этапу

Самый дорогой заказ без скрытых потерь

В отчёте суммы в копейках: [0, -250, 980, 980, 410]. Напишите функцию, которая возвращает первую позицию максимального значения: (2, 980). Отрицательные суммы — возвраты, они допустимы. Для пустого входа договоритесь возвращать None; для значений bool и строк — ValueError. После этого найдите пары разных позиций с разницей сумм не меньше заданного порога; отдельно объясните, считаете ли вы пары позиций или уникальные пары значений.

Проверка результата

  • Все отрицательные суммы и одинаковые максимумы дают правильный результат.
  • Для [10, 10, 40] и порога 30 получаются две пары позиций и одна пара значений.
  • Отрицательный порог отклоняется; сложность поиска пар объяснена.
Подсказка — после своей попытки

Инициализировать максимум нулём опасно для списка возвратов.

Усложнение: Сравните двойной цикл и решение после сортировки, учитывая стоимость самой сортировки.

Вернуться к этапу

План посадки без общих строк

Сформируйте прямоугольную сетку мест rows × cols, начиная с номера start, по строкам. Для (2, 3, 11) нужен результат [[11, 12, 13], [14, 15, 16]], суммы строк [36, 45] и столбцов [25, 27, 29]. Размеры должны быть положительными целыми. Объясните, что произойдёт при построении сетки через повторение одного списка, и проверьте заменой одного места.

Проверка результата

  • Номера уникальны и последовательны.
  • Замена ячейки не меняет другие строки.
  • Случаи 1×1, 1×4 и неверные размеры проверены.
Подсказка — после своей попытки

Проверьте идентичность строк сетки.

Усложнение: Принимайте список недоступных мест и считайте только доступные.

Вернуться к этапу

Поиск блюда по короткому коду

Касса ищет код как подпоследовательность названия: символы должны идти в том же порядке, но между ними могут быть пропуски. Только ASCII-буквы, регистр игнорируем. matches("tea", "steak") даёт True, matches("tea", "eat") — False. Пустой код подходит к любому названию. Реализуйте проверку и объясните, почему сравнение наборов символов не подходит.

Проверка результата

  • Повторяющиеся буквы сохраняют требование к количеству и порядку.
  • aa не подходит к a; aa подходит к banana.
  • Решение делает один проход по названию.
Подсказка — после своей попытки

Храните позицию следующей буквы кода.

Усложнение: Добавьте отдельный режим поиска непрерывной подстроки и покажите различие тестами.

Вернуться к этапу

Чистый комментарий для кухни

Напишите clean_note(text): удалите только буквальные токены :-) и :-(, затем замените первое и последнее вхождения ! точкой. Если ! один, замените его один раз. Остальные символы и пробелы сохраняются. Для "Hot! :-) urgent!!" нужен результат "Hot. urgent!.". Второй функцией уберите повторяющиеся символы из кода блюда, сохранив первое появление: "abacb" → "abc".

Проверка результата

  • Пустая строка, отсутствие ! и одно ! проверены.
  • Обычные скобки и двоеточия не удаляются.
  • Пример с двумя пробелами остаётся с двумя пробелами.
Подсказка — после своей попытки

Отделите удаление токенов от поиска крайних позиций.

Усложнение: Опишите, как изменится контракт при нормализации пробелов; не включайте её молча.

Вернуться к этапу

Сжатие сигналов табло

Табло кухни пишет последовательность строковых статусов. Сжимайте соседние одинаковые значения в пары (status, count): ["ready", "ready", "busy", "ready"] → [("ready", 2), ("busy", 1), ("ready", 1)]. Реализуйте также восстановление исходной последовательности. Статус может содержать цифры и разделители; формат должен оставаться однозначным.

Проверка результата

  • Равные значения, разделённые другим статусом, не объединяются.
  • Восстановление сжатого входа даёт исходный список.
  • Нулевые, отрицательные и нецелые счётчики при восстановлении отклоняются.
Подсказка — после своей попытки

Храните отдельно текущее значение и число повторений.

Усложнение: Сделайте потоковый генератор, который не удерживает весь вход в памяти.

Вернуться к этапу

Обратный справочник аллергенов

Дан справочник {"dish-7": "nuts", "dish-2": "milk", "dish-9": "nuts"}. Постройте обратный: {"nuts": ["dish-7", "dish-9"], "milk": ["dish-2"]}. Порядок кодов соответствует входу. Затем разберите вложенный справочник вида {"lunch": {"dish-7": 2}, "dinner": {"dish-7": 1}}: верните записи с полным путём, не теряя одинаковые ключи из разных веток.

Проверка результата

  • Коллизия значений не приводит к перезаписи.
  • Для вложенного примера пути различаются: (lunch, dish-7) и (dinner, dish-7).
  • Пустой словарь и глубина более двух проверены; договоритесь о допустимых типах листьев.
Подсказка — после своей попытки

Обратное отображение здесь имеет несколько значений на один ключ.

Усложнение: Добавьте переименование кодов с явной ошибкой при коллизии новых ключей.

Вернуться к этапу

Позиция меню в множестве

Создайте неизменяемый MenuItem(menu_id, code, title). Равенство и хеширование зависят только от (menu_id, code): названия могут отличаться. Две позиции с одинаковым кодом из разных меню должны оставаться разными. Добавьте понятные str и repr; продемонстрируйте вывод отдельного объекта и списка объектов.

Проверка результата

  • Равные объекты имеют одинаковый hash и дедуплицируются в set.
  • Изменить поля после добавления в set нельзя.
  • Сравнение с объектом другого типа обработано корректно.
Подсказка — после своей попытки

Сначала сформулируйте, что определяет идентичность позиции в вашем домене.

Усложнение: Объясните, почему вывод списка использует другой метод представления, чем print одного объекта.

Вернуться к этапу

Наборы контейнеров для заказа

Кухня выдаёт упаковки контейнеров: есть одна упаковка по 6 и две по 4. Реализуйте вызываемый объект PackStock: вызов с нужным количеством выбирает целые упаковки с точной суммой и уменьшает остаток. Для запроса 8 выдайте две упаковки по 4; для 5 откажите без изменения склада. Минимизируйте число упаковок, а при равенстве предпочитайте больший размер. Размеры и количество на входе положительные целые; нулевое количество в остатке допустимо.

Проверка результата

  • Жадный выбор самой большой упаковки не должен терять решение для 8.
  • Нельзя выдать больше упаковок, чем хранится.
  • Неудачный поиск не оставляет частичных изменений.
Подсказка — после своей попытки

Отделите поиск комбинации от применения результата к складу.

Усложнение: Задайте лимит на размер входа и объясните, когда нужен другой алгоритм.

Вернуться к этапу

Ромб наследования в обработчике

Создайте Base, Audit(Base), Cache(Base) и Endpoint(Audit, Cache). Каждый handle() добавляет имя класса в общий список; промежуточные классы вызывают super().handle(), Base завершает цепочку. Предскажите порядок вызовов, затем проверьте его через Endpoint.__mro__. Исправьте вариант, где Audit напрямую вызывает Base и пропускает Cache. Объясните различие атрибута класса и атрибута экземпляра на списке записей аудита.

Проверка результата

  • Порядок: Endpoint, Audit, Cache, Base; Base вызван один раз.
  • Перестановка базовых классов меняет порядок предсказуемо.
  • Логи разных экземпляров не смешиваются случайно.
Подсказка — после своей попытки

В цепочке super следующий класс определяется MRO конкретного объекта.

Усложнение: Сравните эту иерархию с композицией независимых обработчиков.

Вернуться к этапу

Регистрация типов уведомлений

Есть конфигурация {"email": {"retry_limit": 2}, "screen": {"retry_limit": 0}}. Создайте тип обработчика для каждого ключа через обычную фабрику классов; затем покажите эквивалент с type(). Экземпляры должны хранить отдельную очередь сообщений. Повторная регистрация имени — явная ошибка. Обсудите, когда динамические классы проигрывают обычному словарю обработчиков и почему Singleton в одном Python-процессе не координирует несколько процессов.

Проверка результата

  • Имена типов и retry_limit соответствуют конфигурации, включая 0.
  • Очереди независимы и повторная регистрация отклоняется.
  • Объяснены границы решения без обещания глобального Singleton.
Подсказка — после своей попытки

Не храните изменяемую очередь в атрибуте созданного класса.

Усложнение: Добавьте явную валидацию конфигурации до создания любого типа.

Вернуться к этапу

Каждый callback отправляет свой статус

Создайте три callback для статусов queued, cooking, ready внутри цикла. После завершения цикла каждый callback должен возвращать свой статус и переданный order_id. Покажите неправильный вариант, где все возвращают ready, объясните время связывания переменной и исправьте его двумя способами. В публичной функции сделайте order_id позиционным, а status именованным аргументом.

Проверка результата

  • Три callback дают три разных статуса для одного order_id.
  • Подпись функции отвергает передачу status позиционно.
  • Исключение отправителя не заменяется общим успешным ответом.
Подсказка — после своей попытки

Сравните замыкание на переменную с сохранением текущего значения в отдельной области.

Усложнение: Напишите тесты, которые вызывают callbacks после завершения регистрации.

Вернуться к этапу

Дежурства по кругу

Напишите cycle_staff(names, start=0), который бесконечно отдаёт имена по кругу. Вход — конечная коллекция; снимок списка фиксируется при создании итератора. Для ["A", "B", "C"] и start=1 первые пять значений: B, C, A, B, C. Пустой вход должен завершаться без значений. Дополнительно напишите конечную обёртку, берущую не больше n дежурств.

Проверка результата

  • Изменение исходного списка после создания итератора не меняет расписание.
  • Нулевой n даёт пустой результат, отрицательный отклоняется.
  • Проход не создаёт бесконечный список в памяти.
Подсказка — после своей попытки

Тело generator-функции начинает выполняться при первом next; учтите это для снимка.

Усложнение: Добавьте генератор накопительных итогов количества дежурств для каждого сотрудника.

Вернуться к этапу

Потоковый отчёт по позициям чека

В файле каждая строка имеет формат dish_id;quantity;price_minor. Пример: 7;2;125, 9;1;300, 7;1;125. Сделайте генератор записей и подсчёт общей суммы: 675. Пустые строки пропускайте. Некорректная строка вызывает ошибку с её номером; quantity — положительное целое, цена — неотрицательное целое. Файл может быть больше доступной памяти.

Проверка результата

  • Итог примера равен 675; строка с нулевой ценой допустима.
  • Номер ошибки относится к исходному файлу, включая пустые строки.
  • Закрытие итератора освобождает файл; весь файл не загружается сразу.
Подсказка — после своей попытки

Отделите разбор одной строки от владения файловым ресурсом.

Усложнение: Верните суммы по dish_id и объясните, от чего теперь зависит память.

Вернуться к этапу

Декоратор аудита результата

Реализуйте audit(operation, sink) для синхронной функции. sink получает ровно одну запись: operation, outcome (ok или error) и длительность. Аргументы и возвращаемое значение функции сохраняются; её исключение должно продолжить распространяться. Пароли и значения аргументов в запись не попадают. Время измеряется монотонными часами; sink в этом упражнении не выбрасывает исключений.

Проверка результата

  • Успех и исключение дают по одной записи.
  • Имя, документация и сигнатура доступны через wraps.
  • Тест подменяет часы и sink, не ждёт реального времени.
Подсказка — после своей попытки

Подумайте, какая часть должна выполняться и при успехе, и при исключении.

Усложнение: Для sink, который может упасть, явно выберите политику: какой результат увидит вызывающий код.

Вернуться к этапу

Отчёт публикуется только целиком

Реализуйте контекстный менеджер report_writer(path). Он пишет временный файл в той же директории. При успешном выходе новый файл заменяет старый отчёт; при исключении старый остаётся, временный удаляется, исключение не подавляется. Гарантию сформулируйте для одного процесса и обычного успешного завершения: устойчивость к отключению питания — отдельное усложнение.

Проверка результата

  • До выхода старый отчёт читается полностью.
  • Исключение посередине записи не портит старый файл.
  • После успеха и ошибки нет незакрытого файла и временных остатков.
Подсказка — после своей попытки

Жизненный цикл временного файла и замена назначения — разные шаги.

Усложнение: Разберите два одновременных писателя и сформулируйте, какая гарантия ещё нужна.

Вернуться к этапу

Асинхронный сбор цен поставщиков

Напишите collect_quotes(suppliers, fetch, limit, timeout): одновременно выполняется не больше limit вызовов fetch. Результат сохраняет порядок поставщиков и содержит отдельно успех или ошибку каждого вызова. Таймаут каждого поставщика отсчитывается после получения слота выполнения; timeout должен быть больше нуля; отмена всей операции должна отменять её задачи и продолжать распространяться. На входе limit — положительное целое. Добавьте декоратор длительности для async fetch и покажите, почему синхронный sleep внутри него блокирует event loop.

Проверка результата

  • Тестовый fetch фиксирует максимум активных вызовов; он не превышает limit.
  • Один сбой не уничтожает успешные результаты, а отмена не маскируется обычной ошибкой.
  • После завершения не остаётся задач; часы и задержки в тестах управляемые.
Подсказка — после своей попытки

Разделите ошибку одного поставщика и отмену всей операции.

Усложнение: Для CPU-тяжёлого расчёта сравните выполнение в event loop, потоке и процессе на измерениях.

Вернуться к этапу

Контрольная сумма талона

Номер талона — неотрицательное целое без ведущих нулей. Отберите номера, у которых сумма десятичных цифр делится на 7. Для [0, 7, 16, 25, 34, 99] результат [0, 7, 16, 25, 34]; порядок и повторы сохраняются. Строки, bool и отрицательные числа отклоняются. Покажите вариант с целочисленным делением и остатком, затем сравните с преобразованием в строку.

Проверка результата

  • Ноль обработан отдельно и считается подходящим.
  • Все цифры многозначного номера учитываются.
  • Объяснена сложность по числу цифр, а не только по количеству номеров.
Подсказка — после своей попытки

После деления на 10 остаток показывает текущую последнюю цифру.

Усложнение: Выведите все подходящие номера в заданном включительном диапазоне без создания полного списка диапазона.

Вернуться к этапу

Число вариантов дегустационного набора

Из n различных блюд нужно выбрать k без повторов; порядок подачи в этой задаче не учитывается. Напишите menu_combinations(n, k) с точным целым результатом. Для (5, 2) нужен ответ 10, для (0, 0) — 1. Допустимы 0 ≤ k ≤ n ≤ 200; bool и нецелые значения отклоняются. Сравните прямое вычисление через факториалы, сокращаемое произведение и таблицу для серии запросов. Не используйте float для промежуточного результата.

Проверка результата

  • Проверены k=0, k=n и симметрия k ↔ n−k.
  • Недопустимый диапазон вызывает явную ошибку.
  • Для серии запросов измерены повторная работа и память кэша.
Подсказка — после своей попытки

Найдите множители, которые сокращаются до вычисления больших факториалов.

Усложнение: Сделайте генератор всех значений для одного n и проверьте сумму строки: 2**n.

Вернуться к этапу