0

I'm trying to extend xml.ElementTree.Element. The problem is that the constructor gives me a ready made Element instance which I cannot extend without tampering with the source code of xml.

Is there a way to initialize a class that inherits from Element and copy the whole Element attributes into the SubClass?

import xml.ElementTree as ET

root = ET.parse('file.xml').getroot() # retrieves Element instance

class ExtendedElement(ET.Element):
    def __init__(self, element):
        pass
        # somehow initialize the ExtendedElement instance
        # with all methods and attributes of element
        # without copying each attribute individually
        # ie self.attrib = element.attrib

    def custom_method(self):
        print(self.attrib)

ext = ExtendedElement(root)

ext.custom_method()

assert root.attrib == ext.attrib
assert list(ext) == list(root)

Usually I would just go on and call self.__dict__.update(element.__dict__), however Element doesn't seem to have a __dict__ (how is this even possible?).

I want to avoid copying all attributes individually as I might miss some hidden ones without knowing.

skjerns
  • 908
  • 8
  • 17
  • Looks that you need aggregation instead of inheritance. – CristiFati Jan 08 '20 at 15:12
  • I'm a bit confused. what is `element` supposed to be? Is it a class or an instance? How is it different from `ET.Element`? – Karl Jan 08 '20 at 15:13
  • I believe the duplicate will answer your question. You simply need to call `super().__init__()` (possibly while passing through `*args` and `**kwargs`) in `ExtendedElement.__init__` – DeepSpace Jan 08 '20 at 15:13
  • @Karl `element` is an instance of `ET.Element` which is returned from `ET.parse` – DeepSpace Jan 08 '20 at 15:14
  • @DeepSpace: This doesn't solve the problem I'm presenting. This will initialize a new object that inherits from `ET.Element`, but I *already* have a `ET.Element` instance. I cannot intialize a new one that easily. – skjerns Jan 08 '20 at 15:20
  • @skjerns `"This will initialize a new object that inherits from ET.Element"` but that is **exactly** what the code you presented us is doing: `class ExtendedElement(ET.Element)` – DeepSpace Jan 08 '20 at 15:21
  • @DeepSpace No, it will not contain any of the attributes that are part of `root`. It will have all the methods of `Element` inherited, but none of the initializes attributes of the instance `element`, ie.`root.attrib`. I would need to pass through new arguments to `super().__init__(...)`, but as there are `**kwargs` I can never be sure that there are no attributes I'm missing. – skjerns Jan 08 '20 at 15:24
  • @skjerns I see what you mean now, you are correct. In that case CristiFati is correct, composition is what you are looking for. I will unmark the dupe – DeepSpace Jan 08 '20 at 15:35

1 Answers1

1

As @CristiFati suggested in the comments, you should use composition here.

class ExtendedElement:
    def __init__(self, element):
        self.element = element

    def custom_method(self):
        print(self.element.attrib)

ext = ExtendedElement(root)
ext.custom_method()

Then the 2 assertions will work with the correct alternation:

assert root.attrib == ext.element.attrib
assert list(ext.element) == list(root)

If you really need list(ext) to work then you can implement __iter__:

def __iter__(self):
    return iter(self.element)

then the second assertion will work as is:

assert list(ext) == list(root)

Depending on your needs, you can hack ExtendedElement to expose all the attributes that element has by implementing __getattr__:

def __getattr__(self, name):
    return getattr(self.element, name)

then

ext = ExtendedElement(root)
print(ext.attrib)

will output (with my test.xml):

{'b': '1'}
DeepSpace
  • 65,330
  • 8
  • 79
  • 117
  • Thanks. This is indeed a valid work-around. One the other side I assume that there is no easy way to copy an objects attributes into another object. Do you by any chance know why the `element` instance has no `__dict__`? I always thought all instances have this. – skjerns Jan 08 '20 at 15:40
  • @skjerns it's enough to implement `__getattr__` to expose the attributes, see my updated answer. – DeepSpace Jan 08 '20 at 15:45
  • @skjerns Not all objects must have `__dict__` (they should, but it's not a must). see https://stackoverflow.com/questions/46575174/if-an-object-doesnt-have-dict-does-its-class-must-have-a-slots-att – DeepSpace Jan 08 '20 at 15:50
  • Ah thanks! With redirecting`__setattr__` and `__getattr__` at least part of the problem is solved, great! – skjerns Jan 09 '20 at 16:02