AtributeError: can't set attribute for python list property

Question:

I’m working with the python-docx library from a forked version, and I’m having an issue with editing the elements list as it is defined as a property.

# docx.document.Document
@property
def elements(self):
    return self._body.elements

I tried to go with the solution mentioned here but the error AtributeError: can't set attribute still popping out.

Next thing I tried is adding the setter to the attribute derived from self._body and editing the code:

# docx.blkcntnr.BlockItemContainer

@property
def elements(self):
    """
    A list containing the elements in this container (paragraph and tables), in document order.
    """
    return [element(item,self.part) for item in self._element.getchildren()]

I’ve tried to add the setter in both levels but ended up again with the error AtributeError: can't set attribute

The setter I wrote:

@elements.setter
def elements(self, value):
    return value 

The implementation I tired:

elements_list = docx__document.elements
elem_list = []
docx__document.elements = elements_list = elem_list

The main problem with that code is docx__document.elements still contains all the elements that are supposed to have been deleted!

Editing the library was like this:

# Inside docx.document.Document
@property
def elements(self):
    return self._body.elements

@elements.setter
def elements(self, value=None):
    self._body.elements = value
    gc.collect()
    return value

The other part:

# Inside docx.blkcntnr.BlockItemContainer

@property
def elements(self):
    """
    A list containing the elements in this container (paragraph and tables), in document order.
    """
    return [element(item,self.part) for item in self._element.getchildren()]


@elements.setter
def elements(self, value):
    """
    A list containing the elements in this container (paragraph and tables), in document order.
    """
    return value

Related question [Update]


If I did add a setter for this property :

# docx.document.Document

@property
def elements(self):
    return self._body.elements

Should I add also a setter for the property:

# docx.blkcntnr.BlockItemContainer

@property
def elements(self):
    """
    A list containing the elements in this container (paragraph and tables), in document order.
    """
    return [element(item,self.part) for item in self._element.getchildren()]

Because the value of document.elemetns is actually the value from document._body.elements, am I right?

Any help would appreciate it!

Asked By: Ahmad

||

Answers:

First a word of warning: if you edit a library, those changes will be wiped away if/when you upgrade the library.

While your question is still not reproducible, I can see now what the problem is.

Setters need to mutate some kind of state to be useful. They don’t return values.

# Inside docx.document.Document

@elements.setter
def elements(self, value):
    self._body.elements = value


# Inside docx.blkcntnr.BlockItemContainer

@elements.setter
def elements(self, value):
    # FIXME

Unfortunately, I haven’t been able to figure out how to implement docx.blkcntnr.BlockItemContainer.elements.setter in a way that neatly replaces the XML tree.

Since it looks like you want to use it to wipe a document clear, why not simply instantiate a new Document instead, like so?

docx__document = docx.Document()
Answered By: Jasmijn

The main "Attribute Error" issue, @Jasmijn already covered… the setter actually needs to set something.

In regards to how to provide a setter for elements:

First we need to figure out where elements comes from:

  • Document.elements comes from [Document]._body.elements
  • [Document]._body.elements comes from _Body, which inherits BlockItemContainer.elements
  • BlockItemContainer.elements builds its elements list dynamically from [BlockItemContainer]._element.getchildren()
  • [BlockItemContainer]._element is equal to [Document]._element.body
  • [Document]._element comes from extending ElementProxy, and is the first argument passed to Document’s constructor

In a very round-a-bout way, given element passed to Document, the document’s elements are derived from: element.body.getchildren(). (A bit tricky tracking down the lookup chain, but that’s just what you get when there’s a lot of abstraction, or perhaps poor object oriented design)

Now to track down what exactly getchildren() does:

  • Looks like the element passed to Document is from the included oxml package
  • oxml is itself a wrapper around lxml
  • Looks like the relevant classes are actually in Cython. As far as I can tell, the _Element class is where getchildren() is ultimately defined (etree.pyx)
  • getchildren() calls _collectChildren (apihelpers.pxi), which gives you an idea of how the internal element structure is setup

Given that the root implementation is Cython is going to complicate things, but I see that the _Element class implements some additional methods which you could make use of, in particular: clear() and extend().

So a possible implementation (which I’ve tested and appears to work):

# inside docx.document.Document
@elements.setter
def elements(self, lst):
    cython_el = self._element.body
    cython_el.clear()
    cython_el.extend(lst)

I’ll disagree with @Jasmijn here and say you don’t need to provide a setter for BlockItemContainer as well, since that’s a private class.

You could also expose other _Element methods directly on the Document object if desired, like clear().

Answered By: Azmisov