A few months ago, @cfbolz posted this tweet
As a reply, I had tweeted
This is one of the few cases where python gives a confusing error message. Let us take a deeper look into this, and in the process get a chance to understand some CPython internals.
What is a layout?
multiple bases have instance lay-out conflict
In order to understand this message, we need to know what is meant by layout.
Layout refers to how the object is represented in memory. Suppose we have a class like this
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
The memory is laid out like this:
The object consists of various fields that python needs to track. How all those pieces is arranged in memory is called the layout of the object. In python's case, all the information is arranged sequencially in "slots". Each slot stores one piece of data. One of these slots is used to store the __dict__
field which references a dictionary object. Every instance variable is stored in the dictionary as a triplet containing the attribute hash, the attribute name and the attribute value.
Note that python will always allocate a dictionary of a minimum size, so even if you have just one attribute, python will allocate space for multiple entries.
Why cannot python store all the instance data in the slots itself? This is because the object layout is fixed. Once the memory is allocated, the layout is not changed until the object is deleted. This is why python creates a dictionary to store the instance fields. It allows us to dynamically add and remove attributes without having to change the layout.
This design contributes to the dynamic nature of python, but it comes with two disadvantages:
- It takes more memory to create a dictionary and store the fields in that, compared to just storing the fields directly in a slot. Especially creating a whole dictionary when you want to keep just two or three fields is quite wasteful
- It takes longer to lookup attributes, as you need to hash the dictionary keys and lookup the attributes in the dictonary, compared to just looking up the value directly from a slot
Slots
Normally, these would not be a big deal as the extra work is just a little and the dynamic benefits make the trade-offs worthwhile. But when you have huge number of objects of a class being created and used then those little extras can add up to something significant.
To solve this, Python introduced __slots__
. This feature exposes the slot structure used internally by python. It allows the developer to define the attributes of the class beforehand and by doing that, python will use that information to create a more efficient layout. Take this code for instance
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
The layout for this class will be as follows
As you can see, this layout does not have a __dict__
field anymore – so we can no longer add or remove fields dynamically.The upside is that the field values can be retrieved directly from the layout without having to go through the dictionary. This makes it more efficient from a time and memory perspective.
__dict__
field as one of the fields in the __slots__
, and you will get dynamic field creation again. But that would mostly defeat the point of using __slots__
Note that slots are referenced using position, so the x
attribute is configured to use slot 0 and y
attribute is configured for slot 1. (To be more precise, slots are configured by the offset from the start of the object memory)
Slots and Inheritance
When inheritance is involved, the __slots__
on the parent class are automatically inherited. The child class can create additional slots if needed as in this example
class Point3D(Point):
__slots__ = ('z',) # only mention the additional slots
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
The Point3D
class would be laid out like this
Note how it includes the slots from the base class in it, and then adds the additional slots to the layout. The slot configuration is also inherited, so x
is slot 0 and y
is slot 1 is inherited, and then a new configuration is added to z
for slot 2.
Slots and Multiple Inheritance
Okay, this is where things start to get messy (Isn't that always the case with multiple inheritance?). The rule with slots and multiple inheritance is that only one of the parents can have slots. Why this rule?
Let us take an example. Suppose we have these two classes
class PointX:
__slots__ = ('x',)
class PointY:
__slots__ = ('y',)
In PointX
the field x
is assigned to slot 0. Similarly in PointY
the field y
is assigned to slot 0. No problems so far.
Now we try to inherit from both of them
class Point(PointX, PointY):
pass
Remember the class inherits the slots as well as the slot assignments. It inherits the configuration of attribute x
for slot 0 and attribute y
for.... slot 0. Ooops. And this is the problem. Python is unable to create a layout because it cannot merge the layouts of the base classes. And we get the infamous error multiple bases have instance lay-out conflict
We are finally ready to understand what this error means. You get the error when you inherit from more than one base class that uses slots, because python is unable to layout the class due to conflicting layouts in the parent classes.
What changed in Python 3.10?
That brings us back to the original tweet. The code below works in Python 3.9 but it gives the layout conflict error in Python 3.10
class A(AttributeError, OSError): pass
What changed? Well it turns out that AttributeError
started using slots in 3.10 (OSError
has used slots for a while), and when you try to inherit from both, you now get an error. Here is @cfbolz again
Fortunately, this example is specifically created to show the change that has happened in Python 3.10. In practise, you probably have no reason to multiple inherit from two exception classes, and chances you will run into this problem when upgrading to 3.10 are extremely slim.
Did you like this article?
If you liked this article, consider subscribing to this site. Subscribing is free.
Why subscribe? Here are three reasons:
- You will get every new article as an email in your inbox, so you never miss an article
- You will be able to comment on all the posts, ask questions, etc
- Once in a while, I will be posting conference talk slides, longer form articles (such as this one), and other content as subscriber-only