Задать вопрос
@tarp20

Как реализовать базовый класс для динамического создания подклассов без вызова рекурсии в Python?

Я занимаюсь рефакторингом структуры классов в Python и столкнулся с проблемой рекурсии при динамическом создании экземпляров подклассов из базового класса. Изначально у меня был базовый класс генератора (BaseGenerator) и несколько дочерних классов генераторов (ChildGenerator1, ChildGenerator2, ChildGenerator3). Моя цель - вызывать только BaseGenerator, который внутренне должен определять и возвращать правильный экземпляр дочернего генератора.

я попытался реализовать логику в методе __new__ класса BaseGenerator:

class BaseGenerator:
    def __new__(cls, *args, **kwargs):
        if cls is BaseGenerator:
            subclass = cls._determine_subclass(*args, **kwargs)
            return subclass(*args, **kwargs)
        else:
            return super().__new__(cls)

    @staticmethod
    def _determine_subclass(*args, **kwargs):
        # Логика определения подходящего подкласса
        # ...

class ChildGenerator1(BaseGenerator):
    pass

class ChildGenerator2(BaseGenerator):
    pass

class ChildGenerator3(BaseGenerator):
    pass


Однако этот подход привел к проблеме рекурсии при создании экземпляра подкласса.

Я ищу предложения или советы о том, как правильно реализовать этот паттерн на Python. Как я могу изменить свой подход для динамического создания правильного подкласса из BaseGenerator ?
  • Вопрос задан
  • 126 просмотров
Подписаться 1 Простой 2 комментария
Пригласить эксперта
Ответы на вопрос 2
Vindicar
@Vindicar
RTFM!
Пусть родительский класс имеет классовую переменную, храняющую список известных классов потомков и ассоциированные с ними данные.
Потомки должны будут регистрировать себя в этой коллекции - либо явно (через метод или декоратор), либо неявно, через метакласс. Тогда родительский класс будет перебирать эту коллекцию в поисках "подходящего" класса-потомка и создавать его.

Пример:
class Parent:
    known_children = []  # список классов-потомков Parent
    @staticmethod
    def register(klass):  # декоратор для регистрации классов-потомков
        Parent.known_children.append(klass)
        return klass
    @classmethod
    def can_handle(cls, data):  # "эй, потомок, ты можешь себя создать из этих данных?"
        raise NotImplementedError()
    @staticmethod
    def make(data):  # создаёт экземпляр одного из потомков
        for child in Parent.known_children:
            if child.can_handle(data):  # потомок согласился обработать данные?
                return child(data)  # вызываем конструктор класса-потомка 
        else:  # относится к for ... in !
            raise TypeError(f'Никто не знает, что делать с этим:\n{data!r}')
    # тут остальная начинка класса
    ...

# а это пример потомка
@Parent.register  # явная регистрация потомка через декоратор
# так удобнее, потому что так можно создавать промежуточных потомков,
# которые не будут реально использоваться - только в наследовании
class SomeChild(Parent):
    @classmethod
    def can_handle(cls, data):
        return data.get('name', None) == 'SomeChild'  # критерий для определения - наш случай или нет?
    
    def __init__(self, data):
        self.x = data['x']
        self.y = data['y']

c = Parent.make({'name': 'SomeChild', 'x': 42, 'y': 69})  # создаст экземпляр SomeChild
print(c)
try:
    Parent.make({'name': 'invalid'})  # потерпит неудачу
except TypeError as err:
    print(err)
Ответ написан
Комментировать
AshBlade
@AshBlade
Просто хочу быть счастливым
Во-первых, этот паттерн называется абстрактная фабрика.
Во-вторых, __new__ используется для выделения памяти, так что не надо его перегружать
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы