Как расширить объект родительского класса в дочерний?

У меня есть класс Operator, который будет добавлять ряд полезных методов и, быть может, атрибутов к xml-элементу, характеризующему оператор алгоритма Метрополиса-Гастингса:

class Operator(lxml.etree._Element):
    tune_attributes = {
        "scaleOperator": "scaleFactor",
        "deltaExchange": "delta",
        "upDownOperator": "scaleFactor",
    }
    loggable = True

    def tune(self, n: str | float) -> NoReturn:
        if loggable:
            print(
                f"Tuning {self.tag} (idrefs: {', '.join(ref for ref in self.xpath('descendant::*/@idref'))}) from {self.attrib[tune_attributes[self.tag]]} to {n}"
            )
        self.attrib[tune_attributes[self.tag]] = n

Операторы получаются через метод xpath из lxml. Результат – это список объектов типа lxml.etree._Element.

Хотелось бы, что бы сохраняя все свои свойства они превратились в соответсвующие объекты класса Operator, просто неся теперь набор дополнительных методов.

И здесь я в затруднении, как это красиво, практично и по-питонячьи провернуть.

_Element не имеет своего собственного метода __init__, поэтому я не знаю, возможно ли это как-то через super().__init__.

Добавлять новые методы сразу к классу _Element через lxml.etree._Element.tune = tune выглядит нехорошей практикой, так как они нужны только операторам, а не всем возможным элементам xml-дерева.

Добавление всего набора методов к каждому полученному оператору по отдельности через types.MethodType(tune, some_operator) всё равно похоже на костыль. Да и класс в последних двух случаях не меняется.

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

class Operator(lxml.etree._Element):
    def __new__(cls, element):
        element.__class__ = cls
        return element

Можете, пожалуйста, подсказать хороший с точки зрения практичности, явности и хорошего стиля разработки способ превратить объект одного класса в дочернего с сохранением всех свойств?


Ответы (1 шт):

Автор решения: Vovin

После долгого прошаривания темы не нашёл у других людей ничего лучше моего варианта с __new__. Например, вот ответ с заморского. В отличие от его автора, считаю, что если не предполагается или отсутствует возможность создать и проинициализировать объект независимо, то лучше использовать __new__ как встроенный конструктор, а не @classmethod.

Таким образом:

Element = lxml.etree._Element

class Operator(Element):
    def __new__(cls, element: Element):
        assert isinstance(element, Element)
        element.__class__ = cls
        assert isinstance(element, Operator)
        return element
→ Ссылка