# 12. Object Layout and Type Slots

# 12. Object Layout and Type Slots

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:

```text
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:

```text
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:

```c
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:

```text
PyLongObject
    PyObject header
    integer-specific fields

PyListObject
    PyObject / PyVarObject header
    list-specific fields

PyFunctionObject
    PyObject header
    function-specific fields
```

Because 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:

```c
typedef struct {
    PyObject_HEAD
    double value;
} FloatLikeObject;
```

The memory shape:

```text
+--------------------+
| ob_refcnt          |
+--------------------+
| ob_type            |
+--------------------+
| value              |
+--------------------+
```

All instances of this type have the same size.

Examples of mostly fixed-size object structs:

```text
float
function
module
cell
method
weakref
many iterator objects
many descriptor objects
```

Fixed-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:

```c
typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size;
} PyVarObject;
```

Extension types use:

```c
typedef struct {
    PyObject_VAR_HEAD
    PyObject *items[1];
} ArrayLikeObject;
```

The memory shape:

```text
+--------------------+
| 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:

```text
tuple object
    header
    size = 3
    item[0] ---> object A
    item[1] ---> object B
    item[2] ---> object C
```

A list stores a pointer to a separate item array:

```text
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:

```text
tp_basicsize
tp_itemsize
tp_dictoffset
tp_weaklistoffset
tp_flags
```

Important 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:

```text
tp_basicsize = sizeof(MyObject)
tp_itemsize = 0
```

For a variable-size type:

```text
tp_basicsize = fixed header and fields
tp_itemsize = size of one trailing element
```

Allocation then computes:

```text
total bytes = tp_basicsize + n * tp_itemsize
```

This 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:

```text
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 slots
```

When Python evaluates:

```python
x + y
```

CPython dispatches through type slots.

When Python evaluates:

```python
x[i]
```

CPython uses sequence or mapping slots.

When Python evaluates:

```python
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:

```text
tp_new
tp_init
tp_alloc
tp_dealloc
tp_free
```

Typical creation path:

```text
call class
    tp_new allocates or returns object
    tp_init initializes object
    return object
```

Example:

```python
obj = MyClass(1, 2)
```

Conceptually:

```text
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:

```c
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:

```c
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:

```text
tp_repr
tp_str
```

Python code:

```python
repr(obj)
str(obj)
```

maps to type behavior.

Example type implementation sketch:

```c
static PyObject *
Counter_repr(CounterObject *self)
{
    return PyUnicode_FromFormat("Counter(%ld)", self->value);
}
```

Then:

```python
repr(counter)
```

can produce:

```text
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:

```text
tp_getattro
tp_setattro
tp_getattr
tp_setattr
```

Modern types usually use `tp_getattro` and `tp_setattro`.

Python code:

```python
obj.name
obj.name = value
del obj.name
```

flows through attribute machinery.

For ordinary objects, generic attribute lookup handles:

```text
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:

```text
tp_descr_get
tp_descr_set
```

Python-level equivalents:

```python
__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.

```python
class Counter:
    def inc(self):
        return 1

c = Counter()
m = c.inc
```

Conceptually:

```text
look up inc on Counter
find function object
function descriptor binds self = c
return bound method
```

Descriptors 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:

```text
tp_call
```

Python code:

```python
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:

```python
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:

```text
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_index
```

Python syntax:

```python
a + b
a - b
a * b
-a
bool(a)
a & b
a << b
```

uses 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:

```python
a + b
```

CPython may consider:

```text
left operand type
right operand type
subclass relationships
left slot
right reflected slot
NotImplemented result
```

Python-level methods:

```python
__add__
__radd__
__iadd__
```

map to internal binary operation machinery.

A type can return `NotImplemented` to let the other operand participate.

Example:

```python
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:

```text
sq_length
sq_concat
sq_repeat
sq_item
sq_ass_item
sq_contains
sq_inplace_concat
sq_inplace_repeat
```

Python syntax:

```python
len(x)
x[i]
x[i] = value
item in x
x + y
x * n
```

may 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:

```text
mp_length
mp_subscript
mp_ass_subscript
```

Python syntax:

```python
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:

```python
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:

```text
list.__getitem__(index)
    index must be integer-like

dict.__getitem__(key)
    key may be any hashable object
```

At 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:

```text
tp_iter
tp_iternext
```

Python code:

```python
for item in obj:
    ...
```

roughly means:

```text
iterator = iter(obj)
while true:
    try:
        item = next(iterator)
    except StopIteration:
        break
```

At C level:

```text
tp_iter
    returns iterator object

tp_iternext
    returns next item or signals StopIteration
```

An 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:

```text
tp_as_buffer
```

Objects such as `bytes`, `bytearray`, `memoryview`, arrays, and numerical extension types can participate.

The buffer protocol matters for:

```text
zero-copy I/O
binary parsing
NumPy interop
memoryview
file and socket operations
serialization
```

A 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:

```text
tp_traverse
tp_clear
```

`tp_traverse` reports contained Python references to the collector.

```c
static int
Box_traverse(BoxObject *self, visitproc visit, void *arg)
{
    Py_VISIT(self->value);
    return 0;
}
```

`tp_clear` breaks contained references during cyclic collection.

```c
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:

```c
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.

```c
.tp_members = Point_members
```

This 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:

```c
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:

```c
.tp_getset = Point_getset
```

A getset entry behaves like a descriptor.

Python code:

```python
p.norm
```

calls the getter.

## 12.24 Method Tables

C extension methods are exposed through method tables.

Conceptual pattern:

```c
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:

```c
.tp_methods = Counter_methods
```

Python code:

```python
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:

```c
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:

```c
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.

```python
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:

```text
base class layout
method resolution order
descriptor behavior
special method lookup
type flags
GC support
memory offsets
```

This is why type creation is a substantial runtime operation.

## 12.28 Special Method Lookup

Special method lookup often bypasses normal instance lookup.

Example:

```python
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:

```python
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:

```text
BaseA requires C layout A
BaseB requires C layout B
Derived(BaseA, BaseB)
    cannot combine both layouts safely
```

Pure 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:

```text
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 memory
```

A Python expression is usually a path through type slots.

```python
result = obj[key] + other
```

Conceptually:

```text
load obj
dispatch subscription through mapping or sequence slot
load other
dispatch addition through number slots
store result
```

## 12.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.

