Как расширить объект родительского класса в дочерний?
У меня есть класс 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 шт):
После долгого прошаривания темы не нашёл у других людей ничего лучше моего варианта с __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