# 7. The Python Object Model

# 7. The Python Object Model

The Python object model is the foundation of CPython. Everything that runs in Python eventually becomes an operation on objects: integers, strings, lists, modules, functions, classes, exceptions, frames, and even compiled code.

At the language level, Python says every object has an identity, a type, and a value. Object identity stays fixed after creation, `is` compares identity, and `id()` returns an integer representing that identity. ([Python documentation][1])

CPython implements this model with C structures, object headers, reference counts, type objects, and operation slots.

## 7.1 Object, Value, and Identity

A Python object has three core properties.

| Property | Meaning                            | Example                 |
| -------- | ---------------------------------- | ----------------------- |
| Identity | The object’s stable identity       | `id(x)`                 |
| Type     | The object’s runtime type          | `type(x)`               |
| Value    | The data represented by the object | `42`, `"abc"`, `[1, 2]` |

Example:

```python
x = [1, 2, 3]
y = x

print(x is y)        # True
print(type(x))       # <class 'list'>
print(x)             # [1, 2, 3]
```

`x` and `y` are two names bound to the same object. They have the same identity.

```python
x.append(4)
print(y)             # [1, 2, 3, 4]
```

The list changed. The binding did not create a copy.

In CPython, object identity is closely tied to object address for normal objects. The language only promises a stable identity, not that identity must be a memory address.

## 7.2 Names Bind to Objects

Python variables are bindings, not storage boxes.

This code:

```python
a = 10
b = a
```

does not copy the integer value into `b`. It binds `b` to the same object referenced by `a`.

For immutable objects this often feels like value copying:

```python
a = 10
b = a
a = 20

print(b)             # 10
```

But the object `10` did not change. The name `a` was rebound to another object.

For mutable objects the difference is visible:

```python
a = []
b = a

a.append("x")

print(b)             # ['x']
```

The name `a` and the name `b` refer to the same list. Mutating through one reference is visible through the other.

This name-to-object model is central to CPython. Bytecode instructions mostly load object references, store object references, pass object references, and call operations on object references.

## 7.3 Every Runtime Value Is a `PyObject *`

Inside CPython, objects are normally handled through pointers of type `PyObject *`. The official C API documentation states that every pointer to a Python object can be cast to `PyObject *`, and that normal release builds store a reference count and a pointer to the corresponding type object in the base object structure. ([Python documentation][2])

Conceptually:

```c
typedef struct {
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;
```

This is simplified, but it captures the essential model.

Every object begins with a shared header:

```text
+--------------------+
| reference count    |
+--------------------+
| type pointer       |
+--------------------+
| object-specific    |
| payload            |
+--------------------+
```

For an integer, the payload stores integer digits.

For a list, the payload stores size information and a pointer to an array of element references.

For a function, the payload stores a code object, globals, defaults, closure cells, and related metadata.

The common header lets the interpreter treat all objects uniformly at the top level.

## 7.4 Fixed-Size and Variable-Size Objects

CPython distinguishes fixed-size objects and variable-size objects.

Fixed-size objects use the base object header.

Variable-size objects include an additional size field. The C API documentation describes `PyVarObject` as an extension of `PyObject` that adds an `ob_size` field for variable-sized objects. ([Python documentation][2])

Conceptually:

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

A tuple is variable-sized because a tuple of length 2 and a tuple of length 100 need different storage.

A bytes object is variable-sized.

A long integer is also variable-sized because CPython stores arbitrary-precision integers using a sequence of digits.

Approximate shape:

```text
PyObject
    used by many fixed-size objects

PyVarObject
    used by objects whose logical size is known at allocation time
```

Examples:

| Object kind | Header kind                 | Reason                          |
| ----------- | --------------------------- | ------------------------------- |
| `float`     | `PyObject`                  | Fixed-size payload              |
| `tuple`     | `PyVarObject`               | Number of elements varies       |
| `bytes`     | `PyVarObject`               | Number of bytes varies          |
| `int`       | `PyVarObject`               | Number of integer digits varies |
| `str`       | Specialized variable layout | String length varies            |

## 7.5 Object Types Define Behavior

An object’s type determines what operations it supports. Python’s data model defines this at the language level: objects have types, and types determine supported operations. ([Python documentation][1])

At the CPython level, the type pointer in the object header points to a `PyTypeObject`.

Conceptually:

```text
object
    ob_refcnt
    ob_type  -------->  type object
                         name
                         size
                         base classes
                         method table
                         number slots
                         sequence slots
                         mapping slots
                         call slot
                         attribute slots
```

When Python evaluates:

```python
x + y
```

CPython does not search for a free-standing `add` function. It asks the relevant type machinery how addition works for those objects.

For integers, addition uses integer-specific code.

For strings, addition uses string concatenation.

For lists, addition creates a concatenated list.

For user-defined classes, addition may call `__add__`.

The syntax is uniform. The implementation is type-directed.

## 7.6 Types Are Objects

A type is itself an object.

```python
print(type(42))      # <class 'int'>
print(type(int))     # <class 'type'>
print(type(type))    # <class 'type'>
```

This recursive structure is intentional.

`int` is an object.

`list` is an object.

User-defined classes are objects.

The type of most type objects is `type`.

```python
class User:
    pass

print(type(User))    # <class 'type'>
print(type(User()))  # <class '__main__.User'>
```

This explains why classes can be assigned, passed, stored, decorated, and created dynamically.

```python
def make_class():
    class Item:
        pass
    return Item

C = make_class()
obj = C()
```

A class statement creates a class object. It binds the class name to that object.

## 7.7 Object Layout and Type Layout

A CPython object’s layout must match what its type object expects.

The CPython source comments in `Include/object.h` note that objects are accessed through `PyObject *`, and object sizes do not change after allocation because moving or resizing objects would require updating references. ([GitHub][3])

That constraint is important.

A list can grow, but the list object itself does not expand in place to hold all elements directly. Instead, it owns a separate element array that can be reallocated.

A tuple cannot grow. Its item references are stored as part of the tuple allocation.

Conceptually:

```text
list object
    header
    current length
    allocated capacity
    pointer to item array  ---> [PyObject*, PyObject*, PyObject*, ...]

tuple object
    header
    length
    inline item references ---> [PyObject*, PyObject*, PyObject*, ...]
```

This difference explains why list append can be amortized efficient while tuple size is fixed.

## 7.8 Mutability

Mutability means an object’s value can change while its identity remains the same.

```python
xs = [1, 2]
before = id(xs)

xs.append(3)
after = id(xs)

print(before == after)   # True
```

The list object remains the same object. Its contents changed.

Immutable objects do not expose operations that change their value in place.

```python
s = "abc"
t = s.upper()

print(s)    # abc
print(t)    # ABC
```

The string operation returns another object.

Common mutable and immutable objects:

| Mutable              | Immutable                                      |
| -------------------- | ---------------------------------------------- |
| `list`               | `int`                                          |
| `dict`               | `float`                                        |
| `set`                | `str`                                          |
| `bytearray`          | `bytes`                                        |
| most class instances | `tuple`, if its contained references are fixed |

A tuple is immutable as a container, but it can contain mutable objects:

```python
t = ([],)
t[0].append(1)

print(t)    # ([1],)
```

The tuple still points to the same list. The list changed.

## 7.9 Reference Semantics

Most CPython runtime operations move references to objects, not objects themselves.

Function calls pass object references.

```python
def add_item(xs):
    xs.append(1)

items = []
add_item(items)

print(items)     # [1]
```

The function receives a reference to the same list object.

Rebinding a local name does not affect the caller:

```python
def replace(xs):
    xs = [1, 2, 3]

items = []
replace(items)

print(items)     # []
```

The name `xs` inside the function was rebound. The original list was not mutated.

This distinction matters for API design. Mutating an object and rebinding a local variable are different operations.

## 7.10 Attributes

Objects can expose attributes.

```python
class User:
    pass

u = User()
u.name = "Ada"

print(u.name)
```

For ordinary user-defined objects, instance attributes are usually stored in an instance dictionary.

Conceptually:

```text
u
    type pointer ---> User
    __dict__ -----> {"name": "Ada"}
```

Attribute lookup is more complex than a direct dictionary lookup. CPython must account for:

```text
data descriptors
instance dictionary
non-data descriptors
class attributes
base classes
__getattribute__
__getattr__
method binding
```

Example:

```python
class User:
    species = "human"

u = User()
u.name = "Ada"

print(u.name)       # instance attribute
print(u.species)    # class attribute
```

If the attribute is absent from the instance, CPython searches the class and its bases.

## 7.11 Methods Are Descriptors

A function stored on a class behaves like a method when accessed through an instance.

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

c = Counter()

print(c.inc)
```

`c.inc` is a bound method. It combines the function object with the instance `c`.

This behavior comes from the descriptor protocol.

A descriptor is an object that defines one or more of:

```python
__get__(self, obj, objtype=None)
__set__(self, obj, value)
__delete__(self, obj)
```

Functions implement `__get__`, so they bind automatically when retrieved from an instance.

Conceptual lookup:

```text
Counter.inc
    raw function object

c.inc
    bound method:
        function = Counter.inc
        self = c
```

Descriptors are a key part of the object model. They implement methods, properties, class methods, static methods, slots, and many extension-level attributes.

## 7.12 Special Methods and Slots

Python syntax maps to special methods.

| Syntax    | Special method |
| --------- | -------------- |
| `x + y`   | `__add__`      |
| `x[i]`    | `__getitem__`  |
| `x()`     | `__call__`     |
| `len(x)`  | `__len__`      |
| `iter(x)` | `__iter__`     |
| `next(x)` | `__next__`     |
| `x in y`  | `__contains__` |
| `str(x)`  | `__str__`      |
| `repr(x)` | `__repr__`     |

At the CPython level, many of these operations correspond to slots in `PyTypeObject`.

For example, a type may provide number slots, sequence slots, mapping slots, and call slots.

This means Python code like:

```python
len(obj)
```

does not simply call `obj.__len__()` through normal instance lookup. CPython uses type-level protocol machinery. This makes common operations faster and more consistent.

## 7.13 Built-in Types Are Ordinary Objects With Privileged Implementations

Built-in types participate in the same object model, but their implementations live in C.

```python
print(type([]))          # <class 'list'>
print(type({}))          # <class 'dict'>
print(type("abc"))       # <class 'str'>
```

The official built-in types documentation groups principal built-in types as numerics, sequences, mappings, classes, instances, and exceptions. ([Python documentation][4])

The difference between `list` and a user-defined class is not that one is an object and the other is not. Both are objects. The difference is that `list` has a C implementation with a fixed internal layout and specialized slots.

A Python list object contains implementation details such as:

```text
object header
logical length
allocated capacity
pointer to element storage
```

A dict contains a hash table implementation.

A string contains Unicode-specific layout and cached metadata.

These internal layouts are optimized for CPython’s runtime behavior.

## 7.14 Classes and Instances

A class defines behavior shared by its instances.

```python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(10, 20)
```

At runtime:

```text
Point
    class object
    type: type
    attributes:
        __init__
        ...

p
    instance object
    type: Point
    attributes:
        x = 10
        y = 20
```

Calling the class invokes construction machinery:

```text
Point(10, 20)
    call type object
    allocate instance through __new__
    initialize instance through __init__
    return instance
```

This is why construction can be customized:

```python
class OnlyOne:
    def __new__(cls):
        print("allocate")
        return super().__new__(cls)

    def __init__(self):
        print("initialize")
```

`__new__` creates or returns an object. `__init__` initializes it.

## 7.15 Inheritance and Method Resolution

Python supports inheritance through classes.

```python
class Animal:
    def speak(self):
        return "?"

class Dog(Animal):
    def speak(self):
        return "woof"
```

When resolving an attribute on an instance, CPython searches according to the class method resolution order, usually called MRO.

```python
print(Dog.__mro__)
```

For single inheritance:

```text
Dog
Animal
object
```

For multiple inheritance, Python uses C3 linearization.

```python
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

print(D.__mro__)
```

The MRO gives a single ordered path for attribute lookup. It prevents ambiguous repeated traversal of shared base classes.

## 7.16 Attribute Lookup Order

For a normal expression:

```python
obj.name
```

CPython performs a structured lookup.

Simplified order:

```text
1. Look for data descriptors on the type or its bases.
2. Look in the instance dictionary.
3. Look for non-data descriptors or other class attributes.
4. If still missing, call __getattr__ if defined.
5. Otherwise raise AttributeError.
```

A data descriptor defines `__set__` or `__delete__`.

A non-data descriptor defines only `__get__`.

This explains why `property` can override an instance dictionary entry:

```python
class User:
    @property
    def name(self):
        return "computed"

u = User()
print(u.name)
```

The property is a data descriptor. It wins over normal instance storage.

## 7.17 Object Creation

Object creation usually has two stages.

```text
allocation
    reserve memory for the object

initialization
    set the initial object state
```

At Python level:

```python
obj = cls.__new__(cls)
cls.__init__(obj)
```

In normal code, calling the class performs both steps.

At C level, the type object controls allocation and initialization through slots such as:

```text
tp_new
tp_init
tp_alloc
tp_dealloc
```

This separation matters because immutable objects need their value during creation.

For example, a tuple or int cannot be created empty and then freely mutated into its final value through public operations. Their final value is established during allocation or construction.

## 7.18 Object Destruction

Object destruction in CPython is mostly reference-count driven.

When an object’s reference count reaches zero, CPython calls its deallocation function.

Conceptually:

```text
Py_DECREF(obj)
    decrement reference count
    if reference count == 0:
        call type-specific deallocator
```

The deallocator releases references owned by the object, frees auxiliary memory, and returns the object’s memory to the allocator.

For containers, deallocation can cascade:

```python
xs = [[1], [2], [3]]
del xs
```

Deleting the outer list decrements references to the inner lists. If those inner lists have no other references, they are also destroyed.

Cycles require cyclic garbage collection, since reference counts alone cannot reclaim objects that keep each other alive.

## 7.19 Equality and Identity

Identity and equality are different.

```python
a = [1, 2]
b = [1, 2]

print(a == b)    # True
print(a is b)    # False
```

`==` asks whether objects compare equal.

`is` asks whether two references point to the same object.

At the CPython level:

```text
is
    compare object pointers

==
    dispatch rich comparison through type machinery
```

Custom classes can define equality:

```python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return (
            isinstance(other, Point)
            and self.x == other.x
            and self.y == other.y
        )
```

Identity remains independent of equality.

## 7.20 Hashing

Hashing supports dictionary keys and set elements.

An object used as a dict key must have a stable hash while it remains in the dictionary.

```python
d = {}
d["name"] = "Ada"
```

Strings are hashable because they are immutable.

Lists are not hashable because their contents can change:

```python
hash("abc")      # works
hash((1, 2))     # works
hash([1, 2])     # TypeError
```

A custom class can define:

```python
__hash__
__eq__
```

The rule is practical: if equality can change, the hash table can break. Mutable objects should generally avoid value-based hashing.

## 7.21 Containers Store References

Python containers store references to objects.

```python
xs = [object(), object(), object()]
```

The list stores references to three objects. It does not inline their complete object data.

Conceptually:

```text
list
    items[0] ---> object A
    items[1] ---> object B
    items[2] ---> object C
```

This explains shallow copy behavior:

```python
a = [[1], [2]]
b = a.copy()

b[0].append(99)

print(a)     # [[1, 99], [2]]
```

The outer list was copied. The inner list objects were shared.

A deep copy recursively copies contained objects:

```python
import copy

a = [[1], [2]]
b = copy.deepcopy(a)
```

## 7.22 Object Protocols

Python relies on protocols rather than explicit interfaces.

An object participates in a protocol by implementing the right methods.

Iteration protocol:

```python
class Count:
    def __init__(self, stop):
        self.current = 0
        self.stop = stop

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.stop:
            raise StopIteration
        value = self.current
        self.current += 1
        return value
```

Container protocol:

```python
class Bag:
    def __init__(self):
        self.items = []

    def __contains__(self, item):
        return item in self.items
```

Context manager protocol:

```python
class Resource:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, tb):
        return False
```

These protocols allow user-defined objects to work with syntax such as:

```python
for x in obj:
    ...

with obj:
    ...

if x in obj:
    ...
```

The object model is the bridge between syntax and behavior.

## 7.23 A Useful Internal Model

A Python expression such as:

```python
result = obj.method(x) + y
```

can be read as object-model operations:

```text
load obj
look up attribute "method"
bind method to obj if descriptor rules apply
load x
call bound method
load y
perform binary addition through type slots
bind result name to returned object
```

Every step moves or creates object references.

Every operation is mediated by type information.

Every result is another object reference.

## 7.24 Minimal C-Level Sketch

A simplified extension type begins with a CPython object header.

```c
typedef struct {
    PyObject_HEAD
    long value;
} CounterObject;
```

The `PyObject_HEAD` macro supplies the standard object header required for CPython to treat this memory as a Python object. The type object then describes how this object behaves.

```c
static PyTypeObject CounterType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "example.Counter",
    .tp_basicsize = sizeof(CounterObject),
    .tp_flags = Py_TPFLAGS_DEFAULT,
};
```

Real extension types need more fields, initialization, methods, error handling, module setup, and reference ownership. But the shape is the same:

```text
instance memory starts with object header
type object describes behavior
runtime manipulates the object through PyObject *
```

## 7.25 Summary

The Python object model says every value has identity, type, and value. CPython realizes this model with `PyObject *` pointers, object headers, reference counts, type objects, and operation slots.

A name binds to an object. A container stores references to objects. A type defines behavior. A class is an object. An instance points to its class. Syntax such as addition, calls, indexing, iteration, and attribute access is implemented through type-directed protocol machinery.

This model is the basis for the rest of CPython: memory management, bytecode execution, attribute lookup, function calls, classes, descriptors, extension modules, and the C API.

[1]: https://docs.python.org/3/reference/datamodel.html?utm_source=chatgpt.com "3. Data model — Python 3.14.5rc1 documentation"
[2]: https://docs.python.org/3/c-api/structures.html?utm_source=chatgpt.com "Common Object Structures"
[3]: https://github.com/python/cpython/blob/main/Include/object.h?utm_source=chatgpt.com "cpython/Include/object.h at main"
[4]: https://docs.python.org/3/library/stdtypes.html?utm_source=chatgpt.com "Built-in Types"

