How CPython implements __slots__
(part 2): access
In the first part I covered how the __slots__
instance attributes were stored (in an ad-hoc array of pointers that
is glued on the end of instance objects), but that's only half of the
puzzle. The other half is letting people access them, and that's what
this entry is about.
As it happens, it's reasonably common in CPython for C-level types to
want to give people Python-level access to fields in C structures,
common enough that the CPython infrastructure for new-style types built
an entire set of machinery to support it. To use this machinery you
specify the names, (C-level) types, offsets, and optionally some flags
like 'read only', for all of the fields that you want people to see and
the type-registration process takes care of all of the rest for you. One
example of where this is used is the __dictoffset__
attribute of
types, which directly reads the tp_dictoffset
field in the C-level type structure.
(I personally find this to be a very cool feature because it avoids a whole lot of potential code duplication in C-level stuff and makes it very easy to expose interesting fields.)
The Python-level manifestation of this machinery is
member_descriptor
objects. Like many things in CPython 2.x, they're
complicated by some backwards compatibility issues with the older C
API for CPython extension modules, so I am going to pass over the fine
details and just say that attempts to read or write these fields winds
up in a big switch statement that checks the type of the field and does
the right magic to read or change it. There are all sorts of field
types supported by this machinery; one of them is T_OBJECT_EX
, where
the field is a pointer to a Python object and an exception is raised if
the pointer is NULL.
You know, that sounds a lot like how __slots__
attributes
behave. That is not a coincidence.
How __slots__
attributes are accessed is that during the process of
registering the type that is the new slot-using class, the core type
machinery dynamically creates appropriate name and offset information
for each attribute slot and registers them exactly as if they were
conventional fields in the basic C structure of the type. This name
and offset information is glued on the end of the basic type
C-level
structure, which is why type
has a non-zero __itemsize__
; each
'item' is a field member registration structure.
(As a trivia note, I believe that this makes type
the only standard
type where using __slots__
in a subclass has absolutely no
effect. Since it has a non-zero itemsize you can't use a non-empty
__slots__
, and since it has a non-zero dictoffset an empty
__slots__
will not stop your subclass from having a __dict__
.)
PS: all of this is for Python 2. I haven't looked at the CPython source for Python 3.x.
Sidebar: where to find this in the CPython source
type
itself is in Objects/typeobject.c. Member descriptors are one of
the classes of descriptors in Objects/descrobject.c. Actually reading
and writing structure members is done by code in Python/structmember.c.
The member list of a type is pointed to by its tp_members
field;
this is the basic raw form, not member descriptors. Member descriptors
are not created immediately on type registration; instead this, and a
lot of other type setup, is deferred until PyType_Ready()
is
called, which is done any time the code needs to look at various type
information like, say, the attribute dictionary.
|
|