Ваш код приводит к бесконечной рекурсии, потому что:
При создании 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__
), и устанавливая атрибуты вручную.
Это гибче, если у вас много подклассов с разной логикой создания атрибута.