Python initsubclass: запрет переопределения констант

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

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

Защита констант от переопределения

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

Чтобы предотвратить случайные ошибки и скрытые конфликты имён, проверка выполняется прямо в момент создания нового класса. Специальный хук инициализации подклассов просматривает собственный словарь атрибутов объявленного наследника. Если потомок пытается задать у себя атрибут с именем, входящим в кортеж PROTECTED_CONSTANTS любого из его прямых или вышестоящих базовых классов, процесс создания немедленно прерывается вызовом TypeError.

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

Что уже дано

В заготовке объявлен базовый класс ProtectedConstants с пустым кортежем PROTECTED_CONSTANTS = () и заготовкой хука __init_subclass__.

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

Реализуйте хук __init_subclass__(cls, **kwargs) в классе ProtectedConstants.

Правила проверки подклассов при их создании:

  • Хук проверяет собственное пространство имён создаваемого класса (cls). Если в нём объявлен атрибут, имя которого содержится в кортеже PROTECTED_CONSTANTS любого из его прямых или более далёких предков, создание класса должно быть отклонено исключением TypeError (допускаются также его подклассы). Текст сообщения исключения не регламентируется.
  • Проверка затрагивает всех предков класса, включая ветви множественного наследования и стандартные примеси (mixins).
  • Защищёнными считаются имена, задекларированные в PROTECTED_CONSTANTS предков. Сам создаваемый класс может одновременно объявить новые имена в собственном кортеже PROTECTED_CONSTANTS и задать для них значения: на этапе создания этого класса они ещё не считаются защищёнными предками.
  • Простое наследование константы без её объявления в теле подкласса разрешено. Если же имя объявлено в самом подклассе, это считается переопределением независимо от значения: установка того же самого значения, ложного значения (None, False, 0, "") или объявление метода с таким именем должно вызывать TypeError.
  • Собственный пустой или дополненный кортеж PROTECTED_CONSTANTS у подкласса не должен отменять защиту, заданную предками выше по иерархии.
  • Обычные незащищённые поля и методы разрешено свободно объявлять и переопределять.
  • Сам атрибут 'PROTECTED_CONSTANTS' также может быть включён в список защищённых имён родительского класса.
  • Защита проверяется строго в момент создания класса через __init_subclass__. Перехватывать последующее присваивание атрибутов у уже созданного класса или гарантировать физическую неизменяемость не требуется.
  • Не забудьте передать необработанные аргументы **kwargs дальше по цепочке инициализации через super().__init_subclass__(**kwargs).

Ограничения проверяемых сценариев:

  • Количество попыток создания классов в рамках одного запуска: от 0 до 512.
  • Глубина наследования: от 1 до 8 уровней; количество прямых родителей: от 1 до 3.
  • Число защищённых имён в одной декларации: от 0 до 8.
  • Имена классов и атрибутов представляют собой ASCII-идентификаторы длиной до 40 символов.
  • Значения полей констант — целые числа (от -1 000 000 до 1 000 000), строки длиной до 40 символов, bool или None; методы — обычные методы экземпляра.
  • В тестах гарантируется корректный порядок разрешения методов (MRO), отсутствие кастомных метаклассов, собственных переопределений __init_subclass__ в наследниках, слотов (__slots__), дескрипторов и служебных dunder-атрибутов.

Ввод и вывод

Стандартные потоки stdin и stdout не используются: считывать ввод и вызывать print в решении не требуется.

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

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

class Transaction(ProtectedConstants):
    PROTECTED_CONSTANTS = ('CURRENCY',)
    CURRENCY = 'RUB'

class Gateway(Transaction):
    TIMEOUT = 30

print(Gateway.CURRENCY, Gateway.TIMEOUT)
try:
    class InvalidGateway(Transaction):
        CURRENCY = 'USD'
except TypeError:
    print('TypeError')

Вывод:

RUB 30
TypeError
РешениеPython

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

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

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