Как правильно изменить класс у объекта?

Рейтинг: 2Ответов: 4Опубликовано: 03.08.2023

Прошу помочь разобраться в питоне,
есть родительский класс и дочерний

class Parent:

    def __init__(self):
        pass

class Child(Parent):

    def __init__(self):
        super().__init__()
        self.foo()
        self.aaa = 'aaa'

    def foo(self):
        print('child foo')

Где-то в начале кода автоматически создается объект родительского класса с некоторыми атрибутами

obj = Parent()
obj.x = 322
obj.y = 69

Далее мне нужно к уже созданному этому объекту добавить методы и атрибуты дочернего класса, я делаю примерно так

...
obj.__class__ = Child
obj.__class__()
print(obj.__dir__())
obj.aaa # AttributeError

и получаю вывод:

child foo
['x', 'y', '__module__', '__init__', 'foo', '__doc__', '__dict__', '__weakref__', '__new__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__reduce_ex__', '__reduce__', '__getstate__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
Traceback (most recent call last):
  File "d:\BACK\izg\4444.py", line 28, in <module>
    obj.aaa # AttributeError
    ^^^^^^^
AttributeError: 'Child' object has no attribute 'aaa'

То есть у объекта сохранились атрибуты родителя x и у, добавился новый дочерний метод foo, но не добавился дочерний атрибут aaa, который мне нужен.

Почему? И как его сейчас получить в объекте?

UPD: уточняю вопрос, в объекте нужно сохранить x, y и добавить к нему foo(), aaa

Ответы

▲ 6Принят

Как правильно изменить класс у объекта?

Правильно - никак. Перезапись поля __class__ - это грязный хак.

Используйте композицию вместо наследования. Если нужно иметь доступ к полям первого объекта из второго объекта, то положите первый объект во второй как отдельный атрибут:

class Parent:
    def __init__(self):
        pass


class Child:
    def __init__(self, parent: Parent):
        self.parent = parent
        self.foo()
        self.aaa = 'aaa'

    def foo(self):
        print('child foo')


obj = Parent()
obj.x = 322
obj.y = 69

child = Child(obj)
print(child.aaa)
print(child.parent.x)
print(child.parent.y)
▲ 2

Просто нужно было вызвать __init__:

obj.__class__ = Child
obj.__class__.__init__(obj)
print(obj.__dir__())
print(obj.x, obj.y, obj.aaa, sep='\n')

Вывод:

child foo
['x', 'y', 'aaa', '__module__', '__init__', 'foo', '__doc__', '__dict__', '__weakref__', '__new__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
322
69
aaa

Но вообще лучше и правда такого не делать. Но если очень хочется, то вот.

▲ 1

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

class Parent:
    def __init__(self):
        self.aaa = "aaa"


class Child(Parent):
    def __init__(self):
        super().__init__()
        self.foo()

    def foo(self):
        print('child foo')


obj = Parent()
obj.x = 322
obj.y = 69

obj.__class__ = Child
obj.__class__()
print(obj.__dir__())
print(obj.aaa)

Результат:

['aaa', 'x', 'y', '__module__', '__init__', 'foo', '__doc__', '__dict__', '__weakref__', '__new__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__reduce_ex__', '__reduce__', '__getstate__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
aaa
▲ 0

Тогда тебе не нужно создавать дочерний класс, просто добавь к существующему необходимый атрибут

class Parent:

    def __init__(self):
        self.x = 0
        self.y = 0
b = Parent()
b.__setattr__("aaa","aaa")
print(b.aaa)