Python initsubclass: иерархический реестр классов

Напишите решение на Python и проверьте его на странице.

Другие упражнения по этой теме: Метаклассы и init_subclass Python: задачи на проверку классов.

Иерархический каталог документации

Модуль документации выстраивает справочные статьи в виде многоуровневого дерева классов. На вершине этой структуры находится корневой класс раздела, который задаёт общее пространство имён. Каждый дочерний подкласс может либо объявить конкретную статью, указав в заголовке строковый идентификатор topic_id, либо остаться промежуточным подразделом без этого параметра.

Хук __init_subclass__ базового класса перехватывает создание каждого нового подкласса и собирает его сквозной путь. Если идентификатор topic_id передан, хук формирует составной ключ вида f"{parent_path}/{topic_id}", где путь предка наследуется от ближайшего родительского раздела, и сохраняет класс в общем словаре каталога. Одинаковые короткие имена тем в разных родительских ветках регистрируются независимо, но любая попытка добавить дубликат полного составного пути должна сразу прерываться ошибкой ValueError.

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

Что уже дано

В редакторе объявлен базовый класс TopicCatalog с атрибутами path = 'docs' и catalog = {}, а также заготовкой метода __init_subclass__(cls, topic_id=None, **kwargs). Вспомогательная проверочная функция observe_topic_catalog доступна при выполнении тестов, её реализовывать не нужно.

Что нужно сделать

Реализуйте метод __init_subclass__(cls, topic_id=None, **kwargs) в классе TopicCatalog:

  • Корневой класс определяет начальный путь path = 'docs' и общий словарь catalog (доступен как TopicCatalog.catalog).
  • Если при объявлении класса-наследника параметр topic_id не передан (None), класс не регистрируется в каталоге. Его атрибут path остаётся унаследованным от ближайшего родительского раздела, чтобы будущие потомки могли продолжить формирование пути от этой секции.
  • Если topic_id передан (строка от 0 до 40 ASCII-символов), сформируйте составной путь f"{parent_path}/{topic_id}", присвойте его атрибуту path создаваемого класса и сохраните сам класс в общий словарь TopicCatalog.catalog по этому ключу.
  • Если сформированный полный путь уже присутствует в каталоге, возбудите исключение семейства ValueError, не заменяя ранее зарегистрированный класс.
  • Обеспечьте кооперативную маршрутизацию аргументов: извлеките topic_id, а все остальные именованные аргументы (**kwargs) без изменений передайте в super().__init_subclass__(**kwargs). Если до object дойдут неизвестные параметры, стандартный механизм вызовет TypeError.
  • Ограничения: дерево поддерживает до 100 последовательных объявлений классов на ветку; конкатенация строк выполняется буквально через f-строку без нормализации слэшей.

Ввод и вывод

Стандартные потоки ввода stdin и вывода stdout в решении не используются: считывать ввод и вызывать print() не нужно. После выполнения кода учащегося проверяющая система считывает из пространства имён модуля класс верхнего уровня TopicCatalog и проверяет регистрацию наследуемых подклассов.

Пример использования

class Guides(TopicCatalog, topic_id='guides'):
    pass
class Intermediate(Guides):
    pass
class Intro(Intermediate, topic_id='intro'):
    pass
class API(TopicCatalog, topic_id='api'):
    pass
class APIIntro(API, topic_id='intro'):
    pass
print(Intro.path)
print(APIIntro.path)
print(TopicCatalog.catalog[Intro.path] is Intro)

Вывод:

docs/guides/intro
docs/api/intro
True
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Связанные задачи