Изменение имени атрибута экземпляра через метакласс

Вопрос или проблема

Мне нужно создать метакласс, который должен добавлять префикс ‘custom_’ ко всем свойствам и методам моего класса CustomClass, за исключением волшебных методов. Вот код:

class CustomClass(metaclass=CustomMeta):
    x = 50

    def __init__(self, val=99):
        self.val = val

    def line(self):
        return 100

    def __str__(self):
        return "Custom_by_metaclass"

Я написал следующий метакласс, который хорошо работает в этом случае, но столкнулся с проблемой, что не могу изменить аргумент экземпляра self.val на self.custom_val, так что мои утверждения не проходят:

inst = CustomClass()
assert inst.custom_val == 99 #Не проходит

В любом случае, другие утверждения проходят успешно:

assert CustomClass.custom_x == 50
inst = CustomClass()
assert inst.custom_x == 50
assert inst.custom_line() == 100
assert str(inst) == "Custom_by_metaclass"

Вот мой метакласс:

class CustomMeta(type):

    def __new__(cls, name_class, base, attrs):
        custom_attrs = {}
        for key, value in attrs.items():
            if not key.startswith('__') and not key.endswith('__'):
                custom_attrs['custom_' + key] = attrs[key]
            else:
                custom_attrs[key] = attrs[key]
        return type.__new__(cls, name_class, base, custom_attrs)

Что мне делать, чтобы решить свою проблему?

PS. Одно из решений, которое пришло в голову, это написать setattr для моего CustomClass

    def __setattr__(cls, name, value):
        if not (name.startswith('__') and name.endswith('__')):
            name = f'custom_{name}'
        super().__setattr__(name, value)

Это работает, и я могу динамически добавлять новые свойства для моего объекта и получать префикс custom_. Но есть ли способ решить эту проблему через метакласс? Задача заключается в создании метакласса, поэтому я сомневаюсь, что моё решение подходит под требования.

Ответ или решение

Чтобы решить вашу проблему с добавлением префикса ‘custom_’ к атрибутам экземпляра класса через метакласс, необходимо понимать, что метаклассы влияют на создание самого класса, но не позволяют изменять значениями экземпляров класса напрямую. Используя метаклассы, мы можем модифицировать атрибуты класса, но не атрибуты экземпляра.

В вашем случае, добавление ‘custom_’ к экземпляру self.val другому атрибуту экземпляра, такому как self.custom_val, должно происходить при инициализации экземпляра класса. Метакласс может изменить поведение класса, но реальные значения атрибутов экземпляра должны устанавливаться внутри метода __init__.

Вот как можно модифицировать ваш класс, чтобы достичь этой цели:

class CustomMeta(type):
    def __new__(cls, name_class, bases, attrs):
        custom_attrs = {}
        for key, value in attrs.items():
            if not key.startswith('__') and not key.endswith('__'):
                custom_attrs['custom_' + key] = value
            else:
                custom_attrs[key] = value
        return super().__new__(cls, name_class, bases, custom_attrs)

class CustomClass(metaclass=CustomMeta):
    x = 50

    def __init__(self, val=99):
        self.custom_val = val  # Изменяем здесь, чтобы установить val как custom_val

    def custom_line(self):  # Добавляем префикс к методу
        return 100

    def __str__(self):
        return "Custom_by_metaclass"

# Теперь тесты должны проходить успешно
inst = CustomClass()
assert inst.custom_val == 99  # Успешно
assert CustomClass.custom_x == 50
inst = CustomClass()
assert inst.custom_x == 50
assert inst.custom_line() == 100
assert str(inst) == "Custom_by_metaclass"

Объяснение изменений:

  1. Инициализация атрибута экземпляра: В методе __init__ теперь мы устанавливаем значение val как self.custom_val. Это гарантирует, что когда экземпляр класса создается, он содержит нужный атрибут с префиксом.

  2. Префиксы для методов: Мы также добавили префикс ‘custom_’ к методу line, чтобы поддерживать единый стиль наименования методов класса.

Почему это работает:

Таким образом, ваша метаклассическая логика отвечает за поведение класса и его атрибуты, в то время как вы непосредственно задаете значение экземпляра в методе инициализации. Исходные условия добавления ‘custom_’ доступны только на уровне класс и атрибутов класса.

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

Оцените материал
Добавить комментарий

Капча загружается...