PyTypeObject slot layout, tp_* function pointers, and how type slots encode object behavior for the interpreter.
CPython represents every runtime value as an object. Each object has a memory layout, and each object’s type describes how that memory should be interpreted.
The object layout answers:
What fields exist inside this object?
Where are the references to other Python objects?
How large is one instance?
Does the object have variable-sized trailing storage?
Does the object participate in cyclic garbage collection?The type slots answer:
How is this object called?
How is it deallocated?
How does attribute lookup work?
How does indexing work?
How does addition work?
How does iteration work?
How is it represented as text?Together, object layout and type slots form the bridge between Python-level behavior and C-level implementation.
12.1 Object Memory Starts With a Header
Every normal CPython object begins with an object header.
Conceptually:
typedef struct {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;This gives every object two fundamental fields:
| Field | Meaning |
|---|---|
ob_refcnt | Reference count |
ob_type | Pointer to the object’s type |
A concrete object places its own fields after this header.
Example shape:
PyLongObject
PyObject header
integer-specific fields
PyListObject
PyObject / PyVarObject header
list-specific fields
PyFunctionObject
PyObject header
function-specific fieldsBecause every object starts with the same header, generic CPython code can manipulate unknown objects through PyObject *.
12.2 Fixed-Size Object Layout
A fixed-size object has the same C struct size for every instance.
Example:
typedef struct {
PyObject_HEAD
double value;
} FloatLikeObject;The memory shape:
+--------------------+
| ob_refcnt |
+--------------------+
| ob_type |
+--------------------+
| value |
+--------------------+All instances of this type have the same size.
Examples of mostly fixed-size object structs:
float
function
module
cell
method
weakref
many iterator objects
many descriptor objectsFixed-size does not mean the object has no references to external storage. A function object has a fixed-size struct, but it points to a code object, globals dictionary, defaults tuple, closure tuple, annotations, and other objects.
12.3 Variable-Size Object Layout
A variable-size object extends the common header with a size field.
Conceptually:
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size;
} PyVarObject;Extension types use:
typedef struct {
PyObject_VAR_HEAD
PyObject *items[1];
} ArrayLikeObject;The memory shape:
+--------------------+
| ob_refcnt |
+--------------------+
| ob_type |
+--------------------+
| ob_size |
+--------------------+
| variable payload |
+--------------------+ob_size has type-specific meaning.
| Type | Meaning of size field |
|---|---|
tuple | Number of elements |
bytes | Number of bytes |
int | Number and sign of internal digits |
| custom variable type | Defined by the type implementation |
ob_size does not mean total memory size in bytes.
12.4 Inline Storage vs Indirect Storage
Variable-sized Python values can store data inline or indirectly.
A tuple stores item references inline in the same allocation:
tuple object
header
size = 3
item[0] ---> object A
item[1] ---> object B
item[2] ---> object CA list stores a pointer to a separate item array:
list object
header
size = 3
ob_item ----+
allocated |
v
[ptr A][ptr B][ptr C][spare...]This difference is fundamental.
A tuple cannot change size after allocation, so inline storage is efficient.
A list must grow and shrink, so it keeps elements in a separate array that can be reallocated without moving the list object itself.
12.5 Type Objects Describe Instance Layout
Each object points to a type object.
The type object stores layout metadata such as:
tp_basicsize
tp_itemsize
tp_dictoffset
tp_weaklistoffset
tp_flagsImportant fields:
| Field | Meaning |
|---|---|
tp_basicsize | Fixed size of an instance |
tp_itemsize | Size of each variable trailing item |
tp_dictoffset | Offset of instance __dict__, if any |
tp_weaklistoffset | Offset of weakref list, if supported |
tp_flags | Type flags describing capabilities |
For a fixed-size type:
tp_basicsize = sizeof(MyObject)
tp_itemsize = 0For a variable-size type:
tp_basicsize = fixed header and fields
tp_itemsize = size of one trailing elementAllocation then computes:
total bytes = tp_basicsize + n * tp_itemsizeThis is how CPython allocates a tuple of length n in one memory block.
12.6 Type Objects Describe Behavior
A type object also stores behavior.
A simplified view:
PyTypeObject
name
size fields
base type
method table
member table
getset table
deallocator
repr function
call function
attribute functions
numeric slots
sequence slots
mapping slots
iterator slotsWhen Python evaluates:
x + yCPython dispatches through type slots.
When Python evaluates:
x[i]CPython uses sequence or mapping slots.
When Python evaluates:
x()CPython uses the call slot.
A type object is both a layout descriptor and a dispatch table.
12.7 The Main Type Slot Categories
PyTypeObject contains many fields. The important categories are:
| Slot category | Purpose |
|---|---|
| Lifecycle slots | allocation, initialization, deallocation |
| Representation slots | repr, str |
| Attribute slots | get, set, descriptor behavior |
| Call slot | function-call syntax |
| Number slots | arithmetic and bit operations |
| Sequence slots | length, indexing, containment, concatenation |
| Mapping slots | dictionary-style lookup and assignment |
| Iterator slots | iteration protocol |
| Buffer slots | raw memory exposure |
| GC slots | traversal and clearing |
| Subclassing slots | inheritance and MRO behavior |
Each category maps Python syntax or runtime behavior to C function pointers.
12.8 Lifecycle Slots
Lifecycle slots control creation and destruction.
Important slots:
tp_new
tp_init
tp_alloc
tp_dealloc
tp_freeTypical creation path:
call class
tp_new allocates or returns object
tp_init initializes object
return objectExample:
obj = MyClass(1, 2)Conceptually:
MyClass.__call__
MyClass.__new__
MyClass.__init__At C level, this flows through type slots.
For immutable objects, tp_new often constructs the full value because the object cannot be modified after creation.
For mutable objects, tp_init can fill or reset fields after allocation.
12.9 Deallocation Slot
tp_dealloc destroys an object whose reference count reached zero.
A simple deallocator:
static void
Box_dealloc(BoxObject *self)
{
Py_XDECREF(self->value);
Py_TYPE(self)->tp_free((PyObject *)self);
}The deallocator must release every Python reference owned by the object.
For GC-aware types, the deallocator must also untrack the object before breaking references:
static void
Box_dealloc(BoxObject *self)
{
PyObject_GC_UnTrack(self);
Py_CLEAR(self->value);
Py_TYPE(self)->tp_free((PyObject *)self);
}A deallocator must be written defensively. Py_DECREF and Py_CLEAR can execute arbitrary Python code indirectly through finalizers.
12.10 Representation Slots
Representation slots support repr() and str().
Important slots:
tp_repr
tp_strPython code:
repr(obj)
str(obj)maps to type behavior.
Example type implementation sketch:
static PyObject *
Counter_repr(CounterObject *self)
{
return PyUnicode_FromFormat("Counter(%ld)", self->value);
}Then:
repr(counter)can produce:
Counter(10)If tp_str is absent, CPython may fall back to tp_repr or generic object formatting behavior depending on the type.
12.11 Attribute Access Slots
Attribute access is one of CPython’s most important paths.
Relevant slots:
tp_getattro
tp_setattro
tp_getattr
tp_setattrModern types usually use tp_getattro and tp_setattro.
Python code:
obj.name
obj.name = value
del obj.nameflows through attribute machinery.
For ordinary objects, generic attribute lookup handles:
data descriptors
instance dictionary
non-data descriptors
class attributes
base classes
__getattr__A custom type can override this behavior by providing custom attribute slots.
12.12 Descriptor Slots
Descriptors implement binding behavior.
A descriptor type can define:
tp_descr_get
tp_descr_setPython-level equivalents:
__get__
__set__
__delete__Functions are descriptors. When a function is stored on a class and accessed through an instance, descriptor logic creates a bound method.
class Counter:
def inc(self):
return 1
c = Counter()
m = c.incConceptually:
look up inc on Counter
find function object
function descriptor binds self = c
return bound methodDescriptors implement methods, properties, class methods, static methods, slots, and many built-in attributes.
12.13 Call Slot
The call slot supports function-call syntax.
Relevant slot:
tp_callPython code:
obj(a, b, c)requires the object’s type to be callable.
Functions, classes, methods, built-in functions, and objects with __call__ all participate in this protocol.
For a user-defined class:
class F:
def __call__(self, x):
return x + 1
f = F()
print(f(10))At the type level, the class machinery ensures instances with __call__ behave as callable objects.
12.14 Number Slots
Number slots handle arithmetic and bitwise operations.
They live in a PyNumberMethods table.
Common conceptual slots:
nb_add
nb_subtract
nb_multiply
nb_remainder
nb_power
nb_negative
nb_positive
nb_absolute
nb_bool
nb_invert
nb_lshift
nb_rshift
nb_and
nb_xor
nb_or
nb_int
nb_float
nb_indexPython syntax:
a + b
a - b
a * b
-a
bool(a)
a & b
a << buses number protocol machinery.
For user-defined classes, special methods such as __add__, __bool__, and __index__ are connected to these slots.
12.15 Binary Operation Dispatch
Binary operations need careful dispatch because two operands have two types.
For:
a + bCPython may consider:
left operand type
right operand type
subclass relationships
left slot
right reflected slot
NotImplemented resultPython-level methods:
__add__
__radd__
__iadd__map to internal binary operation machinery.
A type can return NotImplemented to let the other operand participate.
Example:
class A:
def __add__(self, other):
return NotImplemented
class B:
def __radd__(self, other):
return "handled by B"
print(A() + B())This dispatch behavior is part of Python’s data model and is implemented through type slots.
12.16 Sequence Slots
Sequence slots live in a PySequenceMethods table.
Common conceptual slots:
sq_length
sq_concat
sq_repeat
sq_item
sq_ass_item
sq_contains
sq_inplace_concat
sq_inplace_repeatPython syntax:
len(x)
x[i]
x[i] = value
item in x
x + y
x * nmay use sequence slots.
Lists, tuples, strings, bytes, and ranges all expose sequence behavior, though their internal layouts differ.
12.17 Mapping Slots
Mapping slots live in a PyMappingMethods table.
Common conceptual slots:
mp_length
mp_subscript
mp_ass_subscriptPython syntax:
len(x)
x[key]
x[key] = value
del x[key]may use mapping slots.
Dictionaries are the main mapping type.
A user-defined object that implements __getitem__, __setitem__, and __len__ can participate in mapping-like behavior through type slot integration.
12.18 Sequence vs Mapping Lookup
The same syntax can mean sequence or mapping access:
x[i]For a list, i is an index.
For a dict, i is a key.
The type decides how to interpret the operation.
Conceptually:
list.__getitem__(index)
index must be integer-like
dict.__getitem__(key)
key may be any hashable objectAt C level, CPython dispatches based on the type’s mapping and sequence slots.
12.19 Iterator Slots
Iteration uses type-level protocol support.
Relevant slots:
tp_iter
tp_iternextPython code:
for item in obj:
...roughly means:
iterator = iter(obj)
while true:
try:
item = next(iterator)
except StopIteration:
breakAt C level:
tp_iter
returns iterator object
tp_iternext
returns next item or signals StopIterationAn iterator returns itself from tp_iter.
A container returns a separate iterator object.
12.20 Buffer Slots
The buffer protocol exposes raw memory to other objects without copying.
Relevant slot area:
tp_as_bufferObjects such as bytes, bytearray, memoryview, arrays, and numerical extension types can participate.
The buffer protocol matters for:
zero-copy I/O
binary parsing
NumPy interop
memoryview
file and socket operations
serializationA buffer exporter must keep memory valid while consumers hold views.
This makes buffer slots part of both the object model and memory lifetime model.
12.21 Garbage Collection Slots
GC-aware container types need traversal and clearing support.
Relevant slots:
tp_traverse
tp_cleartp_traverse reports contained Python references to the collector.
static int
Box_traverse(BoxObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->value);
return 0;
}tp_clear breaks contained references during cyclic collection.
static int
Box_clear(BoxObject *self)
{
Py_CLEAR(self->value);
return 0;
}If a type can participate in cycles and does not implement these correctly, it can leak unreachable cycles.
12.22 Member Tables
Some C extension fields can be exposed through member definitions.
Conceptual pattern:
static PyMemberDef Point_members[] = {
{"x", T_LONG, offsetof(PointObject, x), 0, "x coordinate"},
{"y", T_LONG, offsetof(PointObject, y), 0, "y coordinate"},
{NULL}
};The type object points to this table.
.tp_members = Point_membersThis allows CPython to expose C struct fields as Python attributes.
Member tables are useful for simple C fields. More complex attributes usually use getset descriptors.
12.23 Getset Tables
Getset tables expose computed attributes.
Conceptual pattern:
static PyObject *
Point_get_norm(PointObject *self, void *closure)
{
double norm = sqrt(self->x * self->x + self->y * self->y);
return PyFloat_FromDouble(norm);
}
static PyGetSetDef Point_getset[] = {
{"norm", (getter)Point_get_norm, NULL, "vector norm", NULL},
{NULL}
};The type object points to the getset table:
.tp_getset = Point_getsetA getset entry behaves like a descriptor.
Python code:
p.normcalls the getter.
12.24 Method Tables
C extension methods are exposed through method tables.
Conceptual pattern:
static PyObject *
Counter_inc(CounterObject *self, PyObject *Py_UNUSED(ignored))
{
self->value += 1;
return PyLong_FromLong(self->value);
}
static PyMethodDef Counter_methods[] = {
{"inc", (PyCFunction)Counter_inc, METH_NOARGS, "increment counter"},
{NULL}
};The type object points to the method table:
.tp_methods = Counter_methodsPython code:
counter.inc()uses descriptor and method-call machinery to invoke the C function.
12.25 Heap Types and Static Types
CPython has static types and heap types.
Static types are defined as C global structures. Many built-in types historically use static type objects.
Heap types are allocated dynamically at runtime. User-defined Python classes are heap types.
Comparison:
| Type kind | Allocation | Typical use |
|---|---|---|
| Static type | C static storage | Built-in and extension-defined types |
| Heap type | Runtime allocation | Python classes, many modern extension types |
Heap types are more flexible. They support normal class behavior such as dynamic attributes, subclassing metadata, and runtime-managed lifecycle.
Modern extension code often prefers heap types through module definition slots and PyType_FromSpec.
12.26 PyType_FromSpec
PyType_FromSpec creates a type object from a declarative specification.
Conceptual sketch:
static PyType_Slot Counter_slots[] = {
{Py_tp_dealloc, Counter_dealloc},
{Py_tp_methods, Counter_methods},
{0, NULL}
};
static PyType_Spec Counter_spec = {
.name = "example.Counter",
.basicsize = sizeof(CounterObject),
.itemsize = 0,
.flags = Py_TPFLAGS_DEFAULT,
.slots = Counter_slots,
};Then:
PyObject *type = PyType_FromSpec(&Counter_spec);This style avoids directly initializing a large PyTypeObject struct and works better with evolving CPython internals.
12.27 Slot Inheritance
Subclasses inherit behavior from base classes unless they override it.
class A:
def __len__(self):
return 10
class B(A):
pass
print(len(B()))B inherits length behavior from A.
At the C level, type readiness fills inherited slots and computes the final type layout and method resolution metadata.
Slot inheritance must respect:
base class layout
method resolution order
descriptor behavior
special method lookup
type flags
GC support
memory offsetsThis is why type creation is a substantial runtime operation.
12.28 Special Method Lookup
Special method lookup often bypasses normal instance lookup.
Example:
class X:
pass
x = X()
x.__len__ = lambda: 3
len(x)This still raises TypeError because len(x) looks for length behavior on the type, not in the instance dictionary.
Correct:
class X:
def __len__(self):
return 3
x = X()
print(len(x))This rule lets CPython optimize protocol operations and preserves consistent behavior for built-in syntax.
12.29 Layout Conflicts
Multiple inheritance can create layout conflicts.
If two base classes require incompatible C-level instance layouts, CPython may reject the class.
Example shape:
BaseA requires C layout A
BaseB requires C layout B
Derived(BaseA, BaseB)
cannot combine both layouts safelyPure Python classes are usually flexible because their instance state is dictionary-based or slot-based.
C extension types have stricter layout requirements.
This is one reason extension type design should be conservative about inheritance unless subclassing behavior is explicitly needed.
12.30 Mental Model
Use this model when reading type code:
object memory stores state
type object describes layout and behavior
slots map Python operations to C functions
protocols are implemented through slot tables
attribute lookup uses descriptors and dictionaries
numeric, sequence, and mapping syntax dispatch through specialized slots
GC-aware objects must expose references through traversal slots
deallocation must release owned references and free memoryA Python expression is usually a path through type slots.
result = obj[key] + otherConceptually:
load obj
dispatch subscription through mapping or sequence slot
load other
dispatch addition through number slots
store result12.31 Summary
Object layout defines the memory shape of a CPython object. Type slots define what operations that object supports.
PyObject and PyVarObject provide the common headers. Concrete structs add type-specific fields. PyTypeObject records size, offsets, flags, lifecycle hooks, attribute behavior, protocol slots, method tables, member tables, getset tables, and garbage collection support.
This design allows CPython to combine a uniform object representation with highly specialized implementations for built-in and extension types.