Ваш код приводит к бесконечной рекурсии, потому что:
При создании Property(name='myprop') вызывается Property.__init__, который наследует от NamedElement и вызывает NamedElement.__init__.
В NamedElement.__init__ создаётся self.name = Property(name=kwargs.get('name')), что снова вызывает Property.__init__, и цикл повторяется.
Это классическая проблема, когда базовый класс пытается создать атрибут как экземпляр подкласса. Прямой паттерн для этого не существует (как я упоминал ранее), но есть "умные" способы её решить с помощью отложенной инициализации (lazy initialization), фабричного метода или создания экземпляра без вызова __init__ (чтобы избежать рекурсии).
Мы можем модифицировать NamedElement, чтобы он создавал self.name только если это не приведёт к рекурсии. Используем флаг для отслеживания, и object.__new__ для создания экземпляра Property без вызова __init__.
class NamedElement:
def __init__(self, **kwargs):
if not hasattr(self, '_name_created'): # Флаг для предотвращения рекурсии
self._name_created = True
# Создаём экземпляр Property без вызова __init__ (чтобы избежать рекурсии)
self.name = object.__new__(Property)
# Инициализируем его вручную, если нужно (например, установим имя)
if 'name' in kwargs:
self.name._init_name(kwargs['name']) # Кастомный метод для инициализации
class Property(NamedElement):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Дополнительная логика для Property
self.value = "Я - свойство"
def _init_name(self, name_value):
# Кастомная инициализация для имени (без рекурсии)
self.name_value = name_value
# Тестирование
myprop = Property(name='myprop')
print(type(myprop.name)) # <class '__main__.Property'>
print(myprop.name.name_value) # myprop
print(myprop.value) # Я - свойство
print(myprop is myprop.name) # False (разные объекты)
Флаг _name_created: В NamedElement.__init__ проверяется, был ли уже создан self.name. Если нет, создаётся экземпляр Property с помощью object.__new__(Property) — это создаёт объект без вызова __init__, предотвращая рекурсию.
Кастомная инициализация: После создания объекта мы вручную устанавливаем его атрибуты через _init_name, чтобы избежать повторного вызова __init__.
Результат: myprop.name становится экземпляром Property, но без бесконечного цикла. Property может иметь свои собственные атрибуты (например, value).
Если хотите более явный контроль, используйте фабричный метод в NamedElement, который подкласс может переопределить:
class NamedElement:
def __init__(self, **kwargs):
self.name = self.create_name(**kwargs)
def create_name(self, **kwargs):
# По умолчанию возвращает None; Property переопределит это
return None
class Property(NamedElement):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.value = "Я - свойство"
def create_name(self, **kwargs):
# Создаём экземпляр Property без рекурсии
name_obj = object.__new__(Property)
if 'name' in kwargs:
name_obj.name_value = kwargs['name']
return name_obj
# Тестирование
myprop = Property(name='myprop')
print(type(myprop.name)) # <class '__main__.Property'>
print(myprop.name.name_value) # myprop
NamedElement делегирует создание self.name методу create_name.
Property переопределяет create_name, создавая экземпляр Property с object.__new__ (без __init__), и устанавливая атрибуты вручную.
Это гибче, если у вас много подклассов с разной логикой создания атрибута.