Stevoisiak
Stevoisiak

Reputation: 26812

How do I make a class inherit from lxml.objectify.Element?

I have a Python class for a Person object which inherits xml.etree.ElementTree.Element.

from xml.etree import ElementTree
class Person(ElementTree.Element):
    def __init__(self, name, age):
        super().__init__("person", name=name, age=str(age))

person = Person("Paul", 23)

I plan on expanding my Person class to include additional attributes and subtags. Before doing this, I want to simplify attribute access by converting my class to use lxml.objectify.Element.

from lxml import objectify
class Person(objectify.Element):
    def __init__(self, name, age):
        super().__init__("person", name=name, age=str(age))

person = Person("Paul", 23)

Unfortunately, trying to inherit from objectify.Element raises a TypeError about creating cython_function_or_method instances.

Traceback (most recent call last):
  File "C:/Users/svascellar/.PyCharmCE2017.3/config/scratches/scratch_1.py", line 2, in <module>
    class Person(objectify.Element):
TypeError: cannot create 'cython_function_or_method' instances

Why is my class raising a TypeError? How do I inherit frrom lxml.objectify.Element?

Upvotes: 1

Views: 1402

Answers (1)

Stevoisiak
Stevoisiak

Reputation: 26812

In hindsight, I recommend against using lxml to create subclasses.

As the comments mentioned, lxml.objectify.Element() is not a class. It is a factory method that creates and returns an object, meaning it cannot be inherited.

While it's possible to inherit from ElementBase or ObjectifiedElement, the lxml documentation strongly warns that you must not override __init__ or __new__, which my original answer did.

BIG FAT WARNING: Subclasses must not override __init__ or __new__ as it is absolutely undefined when these objects will be created or destroyed. All persistent state of Elements must be stored in the underlying XML. If you really need to initialize the object after creation, you can implement an _init(self) method that will be called directly after object creation.
- Documentation for lxml.etree.ElementBase

I suggest inheriting from a different class instead. As mentioned in the original question, you can inherit from lxml.etree.ElementTree.Element fairly easily.

from xml.etree import ElementTree
class Person(ElementTree.Element):
    def __init__(self, name, age):
        super().__init__("person", name=name, age=str(age))

person = Person("Paul", 23)

Working with sub-classes of lxml may be more trouble than it's worth. However, if you really want to subclass lxml despite the warnings, you can see my original answer below.


Old Answer (Unsafe)

Try inheriting from lxml.objectify.ObjectifiedElement instead.

from lxml import objectify
class person(objectify.ObjectifiedElement):
    def __init__(self, name, age):
        super().__init__(name=name, age=str(age))

my_person = person("Paul", 23)
print(etree.tostring(my_person, encoding="unicode"))
# output: <person age="23" name="Paul"/>

Make sure that your class name matches your XML tag, as ObjectifiedElement uses the class name for the root XML tag name.

As Goyo and bruno desthuilliers pointed out, objectify.Element is not a class, which means you can't inherit from it. Going off their comments, I used type() to confirm this for myself and discovered that objectify.Element() returned an object of type ObjectifiedElement.

from lxml import objectify
person = objectify.Element("person", name="Paul", age="23")
print(type(person))
# output: <class 'lxml.objectify.ObjectifiedElement'>

After changing my class to inherit from lxml.objectify.ObjectifiedElement, the class worked as expected.

Upvotes: 2

Related Questions