AtributeError: can't set attribute for python list property
Asked Answered
R

2

4

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!

Raycher answered 16/12, 2022 at 16:36 Comment(4)
Hi @Jasmijn the code used and the error is added also with steps I took with this issue.Raycher
Thanks for editing your question. Unfortunately it's still not reproducible.Whang
Hi @Whang again, can you check the question now? If it is good can you upvote it also?Raycher
I'm not familiar with this library, but it seems to me you're going about it the wrong way. The elements getter returns a list-comprehension of objects composed of its children (return [element(..) for item in self._element.getchildren()]). The getter always creates a new list from the children, so setting the attribute "element" itself to an empty list will not delete the children. I suggest poking around the instance's _element attribute, perhaps check where def getchildren() gets you to. Look at what is used to create the list, instead of the list itself.Florettaflorette
G
2

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().

Gonococcus answered 17/12, 2022 at 22:55 Comment(1)
Impressive. I got lost somewhere in the process of chasing proxy object to proxy object and wrapper library to wrapper library, all of which seemed to be either poorly documented or completely undocumented. As for our disagreement, that's fair. I was thinking only about a setter on Document that delegated the actual work of changing the elements to self._element which would be my preference.Whang
W
2

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()
Whang answered 16/12, 2022 at 17:39 Comment(8)
Using docx__document = docx.Document() will still create an object in the memory and that is the opposite of what I'm trying. I'm trying to clear all the contents to save some space in the memoryRaycher
> warning: if you edit a library, those changes will be wiped away if/when you upgrade the library. I know that, I'm planning to push the update to its repoRaycher
In that case, you could try del docx__document or docx__document = None.Whang
This will lead to another problem, the garbage collector is not actually -I assume- doing its job because even if I call it manually the memory usage is not droppingRaycher
Hi @Whang I've updated the question, if you can help with the last update I would really appreciate it and I can approve the answer, Thanks!Raycher
Unfortunately, I've given you the best answer I can with the information you provided. Unless and until I can reproduce your actual problem, there is nothing more for me to do except speculate. And it's not even clear to me what your real problem is. Why do you need to reclaim memory?Whang
I mean the other part under Related question [Update] do you have any idea? @jasmijnRaycher
Oh the answer to that is yes.Whang
G
2

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().

Gonococcus answered 17/12, 2022 at 22:55 Comment(1)
Impressive. I got lost somewhere in the process of chasing proxy object to proxy object and wrapper library to wrapper library, all of which seemed to be either poorly documented or completely undocumented. As for our disagreement, that's fair. I was thinking only about a setter on Document that delegated the actual work of changing the elements to self._element which would be my preference.Whang

© 2022 - 2024 — McMap. All rights reserved.