I mentioned
__dictoffset__
in passing in HowSlotsWorkI . Today I feel like expanding on that passing mention with some notes. All of this is specific to CPython.
As mentioned in passing in HowSlotsWorkI , the
__dictoffset__
attribute tells you the offset to where you find the pointer to the
__dict__
object in any instance object that has one. It is in bytes. A positive value is an offset from the start of the object; a negative value is an offset from the end of the object, and is used only for classes derived from types (such as
str
and
long
) that have a variable-sized component. A
__dictoffset__
value of zero means that the type (or class) doesn't have a
__dict__
attribute.
(You can tell which types have a variable-sized component by looking at their
__itemsize__
attribute; zero means that they don't have such a component.)
As sort of discussed in the sidebar in HowSlotsWorkI , if you inherit from something with a zero
__dictoffset__
your subclass will normally have a non-zero
__dictoffset__
and the pointer to the
__dict__
object will be glued on the end of the C-level blob of your basic type.
Most built-in types have a
__dictoffset__
of zero, as you'd expect. However, a few types have a non-zero
__dictoffset__
; the ones I know of are exceptions, functions, modules, and
type
itself. What is going on is that all of these types already have to have some sort of dictionary for their contents, along with a pointer to this dictionary in their basic C-level blob. So they reuse this pointer (and associated dictionary) as their
__dict__
, by pointing
__dictoffset__
directly to this internal field. One consequence of this is that subclasses of these classes always have a
__dict__
, even if your subclass uses
__slots__
.
(In general, once a class has a non-zero
__dictoffset__
all of its subclasses will always have a
__dict__
. I think that you can sometimes still save space and allocations by using
__slots__
, but you don't get any of the other features of
__slots__
that people are sometimes unwisely attracted to.)