20

I have a big tree with hundreds of thousands of nodes, and I'm using __slots__ to reduce the memory consumption. I just found a very strange bug and fixed it, but I don't understand the behavior that I saw.

Here's a simplified code sample:

class NodeBase(object):
    __slots__ = ["name"]
    def __init__(self, name):
        self.name = name

class NodeTypeA(NodeBase):
    name = "Brian"
    __slots__ = ["foo"]

I then execute the following:

>>> node = NodeTypeA("Monty")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __init__
AttributeError: 'NodeTypeA' object attribute 'name' is read-only

There is no error if NodeTypeA.name is not defined (side note: that attribute was there by mistake, and had no reason for being there). There is also no error if NodeTypeA.__slots__ is never defined, and it therefore has a __dict__.

The thing I don't understand is: why does the existence of a class variable in a superclass interfere with setting an instance variable in a slot in the child class?

Can anybody explain why this combination results in the object attribute is read-only error? I know my example is contrived, and is unlikely to be intentional in a real program, but that doesn't make this behavior any less strange.

Thanks,
Jonathan

2
  • NodeTypeA is creating a class variable name and not assigning a value to the instance variable defined in NodeBase. Is this intentional? Commented Apr 22, 2011 at 17:23
  • It wasn't intentional at first - I accidentally had this in my code, causing the bug I'm asking about. But then I got curious about why the code behaves like it does, so I intentionally put it in my code sample. Commented Apr 22, 2011 at 17:52

1 Answer 1

27

A smaller example:

class C(object):
    __slots__ = ('x',)
    x = 0

C().x = 1

The documentation on slots states at one point:

__slots__ are implemented at the class level by creating descriptors (Implementing Descriptors) for each variable name. As a result, class attributes cannot be used to set default values for instance variables defined by __slots__; otherwise, the class attribute would overwrite the descriptor assignment.

When __slots__ is in use, attribute assignment to slot attributes needs to go through the descriptors created for the slot attributes. Shadowing the descriptors in a subclass causes Python to be unable to find the routine needed to set the attribute. Python can still see that an attribute is there, though (because it finds the object that's shadowing the descriptor), so it reports that the attribute is read-only.

Sign up to request clarification or add additional context in comments.

6 Comments

A similar answer is also posted here: stackoverflow.com/questions/820671/…
Good point. I didn't realize that it simplified to that - I thought that inheritance was part of the problem. Clearly, as @Santa points out, this is then the same as that previous question.
Python does not create read-only descriptors here. What happens is that the instance doesn't have a __dict__, and the name = "Brian" string in NodeTypeA hides the slot descriptor created for the name slot in NodeBase. Python sees that the instance has a name attribute (because attribute lookup finds the "Brian" string), but the attribute isn't assignable (because "Brian" doesn't have a __set__ method and the instance doesn't have a __dict__), so it reports that the attribute is read-only.
If you want to see the code, PyObject_GenericSetAttr is the __setattr__ implementation involved.
You can also see that no read-only descriptors are involved by inspecting the type dicts manually. For NodeTypeA, NodeTypeA.__dict__['name'] is a regular string, not a descriptor (and for your C class, C.__dict__['x'] is 0). For NodeBase, NodeBase.__dict__['name'] is a descriptor that supports reads, writes, and deletes, as you can see by calling __get__, __set__, and __delete__ manually.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.