Метакласс Python: регистрация методов в пространстве имён

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

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

Пространство командных методов

Модульный интерфейс терминала позволяет классам-плагинам регистрировать свои методы действий в общем командном меню. Чтобы команды разных модулей не путались между собой, каждый плагин передаёт в заголовке ключевое слово namespace_prefix="имя", а сами целевые методы оформляет со стандартным префиксом do_.

За сборку таких классов отвечает метакласс. В своём методе __new__ он извлекает указанный namespace_prefix, перехватывает сырое пространство имён и преобразует имена команд: служебное do_ заменяется на заданный префикс со знаком подчёркивания, а исходный ключ с do_ удаляется. Если же получившееся целевое имя уже заранее объявлено в пространстве имён класса, метакласс не перезаписывает его, а немедленно прерывает создание класса исключением ValueError.

Реализуйте метакласс, который переименовывает методы действий по переданному префиксу и защищает класс от коллизий с уже существующими атрибутами.

Что уже дано

В редакторе объявлен класс CommandNamespace(type) с заготовкой метода __new__(mcs, name, bases, namespace, **kwargs).

При выполнении решения в среде также доступны готовые проверочные функции observe_command_declarations, inspect_prepared_command_class и observe_command_classcell для сборки тестовых классов и проверки цепочки вызовов. Реализовывать их заново не требуется.

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

Реализуйте метод __new__(mcs, name, bases, namespace, **kwargs) метакласса CommandNamespace.

При создании класса соблюдайте следующие правила:

  • Аргумент namespace_prefix обязателен и явно передаётся при создании любого класса с этим метаклассом (непустая ASCII-строка длиной от 1 до 20 символов). Извлеките его из kwargs перед передачей параметров в type.__new__.
  • Все остальные именованные аргументы заголовка класса должны быть переданы дальше без изменений для кооперативной работы с базовыми классами и хуком __init_subclass__ (например, аргумент tag).
  • В сыром словаре пространства имён namespace найдите все собственные ключи, начинающиеся с do_ и имеющие непустой суффикс (от 0 до 8 методов на класс). Каждое такое имя преобразуется в f"{namespace_prefix}_{suffix}". Исходный ключ do_... удаляется, а функция сохраняется под новым именем с сохранением исходного объекта и его привязки к экземпляру.
  • Если целевое имя уже присутствует в исходном словаре namespace, немедленно возбудите исключение ValueError (или его подкласс) до вызова базового конструктора класса. Это правило распространяется и на случай, когда префикс равен do, а целевое имя совпадает с другим уже существующим собственным ключом do_....
  • Проверяйте на коллизии только текущее сырое пространство имён namespace. Атрибуты базовых классов конфликтом не считаются, а унаследованные методы не должны модифицироваться или переименовываться.
  • Обычные методы, константы (целые числа, логические значения, строки, None), а также служебные атрибуты Python (включая __classcell__, необходимый для работы super() без аргументов) должны оставаться нетронутыми.
  • Конструктор должен сохранять переданный метакласс mcs, чтобы поддерживать корректное наследование от CommandNamespace.

Ввод и вывод

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

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

class Build(metaclass=CommandNamespace, namespace_prefix='build'):
    def do_run(self, value):
        return value * 2
    VERSION = 1
print(Build().build_run(7))
print('do_run' in vars(Build))
try:
    class Conflict(metaclass=CommandNamespace, namespace_prefix='build'):
        build_run = None
        def do_run(self):
            return 1
except ValueError:
    print('ValueError')

Вывод:

14
False
ValueError
РешениеPython

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

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

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