Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This file is generated from `pyxray/src/pyxray/glossary.py`. Edit that and run `

## Index

[ASDL](#asdl) | [Argument Clinic](#argument-clinic) | [EXTENDED_ARG](#extended_arg) | [GC pre header](#gc-pre-header) | [JIT](#jit) | [PEG parser](#peg-parser) | [PyVarObject](#pyvarobject) | [Pyodide](#pyodide) | [WebAssembly](#webassembly) | [abstract syntax tree](#abstract-syntax-tree) | [adaptive instruction](#adaptive-instruction) | [arena](#arena) | [assembler](#assembler) | [backtrace](#backtrace) | [basic block](#basic-block) | [binding](#binding) | [block](#block) | [blurb](#blurb) | [borrowed reference](#borrowed-reference) | [bytecode](#bytecode) | [cell](#cell) | [class cell](#class-cell) | [closure](#closure) | [code generation](#code-generation) | [code object](#code-object) | [coding cookie](#coding-cookie) | [cold block](#cold-block) | [computed goto](#computed-goto) | [configure](#configure) | [constant folding](#constant-folding) | [control flow graph](#control-flow-graph) | [cycle collector](#cycle-collector) | [deallocation](#deallocation) | [debug build](#debug-build) | [deoptimization](#deoptimization) | [devguide](#devguide) | [dispatch](#dispatch) | [environment changed](#environment-changed) | [eval loop](#eval-loop) | [evaluation order](#evaluation-order) | [exception table](#exception-table) | [f string](#f-string) | [finalizer](#finalizer) | [frame](#frame) | [free threaded build](#free-threaded-build) | [free variable](#free-variable) | [gdb](#gdb) | [generated file](#generated-file) | [generation](#generation) | [grammar](#grammar) | [header file](#header-file) | [heap type](#heap-type) | [immortal object](#immortal-object) | [indent and dedent](#indent-and-dedent) | [inline cache](#inline-cache) | [instance dictionary](#instance-dictionary) | [instruction](#instruction) | [interning](#interning) | [left recursion](#left-recursion) | [line table](#line-table) | [magic number](#magic-number) | [marshal](#marshal) | [metaclass](#metaclass) | [monitoring events](#monitoring-events) | [new reference](#new-reference) | [object](#object) | [object header](#object-header) | [obmalloc](#obmalloc) | [oparg](#oparg) | [opcode](#opcode) | [parser generator](#parser-generator) | [pdb](#pdb) | [pointer](#pointer) | [pool](#pool) | [product type](#product-type) | [profile guided optimization](#profile-guided-optimization) | [pseudo instruction](#pseudo-instruction) | [pyc file](#pyc-file) | [pyconfig](#pyconfig) | [reference count](#reference-count) | [reference cycle](#reference-cycle) | [reference leak](#reference-leak) | [regen](#regen) | [regrtest](#regrtest) | [replacement field](#replacement-field) | [resource](#resource) | [scope](#scope) | [segmentation fault](#segmentation-fault) | [short circuiting](#short-circuiting) | [slot](#slot) | [slot wrapper](#slot-wrapper) | [small integer cache](#small-integer-cache) | [soft keyword](#soft-keyword) | [specialization](#specialization) | [stack depth](#stack-depth) | [stack effect](#stack-effect) | [static type](#static-type) | [stolen reference](#stolen-reference) | [struct](#struct) | [sum type](#sum-type) | [symbol table](#symbol-table) | [symbol table pass](#symbol-table-pass) | [t string](#t-string) | [test case](#test-case) | [tier one](#tier-one) | [tier two](#tier-two) | [token](#token) | [tokenizer](#tokenizer) | [trace function](#trace-function) | [type object](#type-object) | [underflow](#underflow) | [value stack](#value-stack) | [weak reference](#weak-reference)
[ASDL](#asdl) | [Argument Clinic](#argument-clinic) | [C3 linearization](#c3-linearization) | [EXTENDED_ARG](#extended_arg) | [GC pre header](#gc-pre-header) | [JIT](#jit) | [MRO](#mro) | [PEG parser](#peg-parser) | [PyVarObject](#pyvarobject) | [Pyodide](#pyodide) | [WebAssembly](#webassembly) | [abstract syntax tree](#abstract-syntax-tree) | [adaptive instruction](#adaptive-instruction) | [arena](#arena) | [assembler](#assembler) | [backtrace](#backtrace) | [basic block](#basic-block) | [binding](#binding) | [block](#block) | [blurb](#blurb) | [borrowed reference](#borrowed-reference) | [bytecode](#bytecode) | [cell](#cell) | [class cell](#class-cell) | [closure](#closure) | [code generation](#code-generation) | [code object](#code-object) | [coding cookie](#coding-cookie) | [cold block](#cold-block) | [computed goto](#computed-goto) | [configure](#configure) | [constant folding](#constant-folding) | [control flow graph](#control-flow-graph) | [cycle collector](#cycle-collector) | [deallocation](#deallocation) | [debug build](#debug-build) | [deoptimization](#deoptimization) | [devguide](#devguide) | [dispatch](#dispatch) | [environment changed](#environment-changed) | [eval loop](#eval-loop) | [evaluation order](#evaluation-order) | [exception table](#exception-table) | [f string](#f-string) | [finalizer](#finalizer) | [frame](#frame) | [free threaded build](#free-threaded-build) | [free variable](#free-variable) | [gdb](#gdb) | [generated file](#generated-file) | [generation](#generation) | [grammar](#grammar) | [header file](#header-file) | [heap type](#heap-type) | [immortal object](#immortal-object) | [indent and dedent](#indent-and-dedent) | [inline cache](#inline-cache) | [instance dictionary](#instance-dictionary) | [instruction](#instruction) | [interning](#interning) | [left recursion](#left-recursion) | [line table](#line-table) | [magic number](#magic-number) | [marshal](#marshal) | [metaclass](#metaclass) | [monitoring events](#monitoring-events) | [new reference](#new-reference) | [object](#object) | [object header](#object-header) | [obmalloc](#obmalloc) | [oparg](#oparg) | [opcode](#opcode) | [parser generator](#parser-generator) | [pdb](#pdb) | [pointer](#pointer) | [pool](#pool) | [product type](#product-type) | [profile guided optimization](#profile-guided-optimization) | [pseudo instruction](#pseudo-instruction) | [pyc file](#pyc-file) | [pyconfig](#pyconfig) | [reference count](#reference-count) | [reference cycle](#reference-cycle) | [reference leak](#reference-leak) | [regen](#regen) | [regrtest](#regrtest) | [replacement field](#replacement-field) | [resource](#resource) | [scope](#scope) | [segmentation fault](#segmentation-fault) | [short circuiting](#short-circuiting) | [slot](#slot) | [slot wrapper](#slot-wrapper) | [small integer cache](#small-integer-cache) | [soft keyword](#soft-keyword) | [specialization](#specialization) | [stack depth](#stack-depth) | [stack effect](#stack-effect) | [static type](#static-type) | [stolen reference](#stolen-reference) | [struct](#struct) | [sum type](#sum-type) | [symbol table](#symbol-table) | [symbol table pass](#symbol-table-pass) | [t string](#t-string) | [test case](#test-case) | [tier one](#tier-one) | [tier two](#tier-two) | [token](#token) | [tokenizer](#tokenizer) | [trace function](#trace-function) | [type object](#type-object) | [underflow](#underflow) | [value stack](#value-stack) | [weak reference](#weak-reference)

## Reading the source

Expand Down Expand Up @@ -658,6 +658,22 @@ When a type written in C is made ready, `add_operators` walks the slot table and

Also written `wrapper_descriptor`. First met in O03. See also [slot](#slot), [type object](#type-object). In the source: [`Objects/typeobject.c:12456-12470@v3.15.0rc1#add_operators`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L12456-L12470).

### MRO

**The flat list of classes, in order, that a name is looked up in.**

Every type carries one as `tp_mro`, computed once when the class is made and recomputed for the whole subtree if `__bases__` is later assigned. It always starts with the type itself and ends with `object`. Attribute lookup, `super`, and the slot table all read this list rather than walking `__bases__`, which is why multiple inheritance has one answer instead of a search.

Also written method resolution order, `__mro__`, `tp_mro`. First met in O04. See also [C3 linearization](#c3-linearization), [type object](#type-object). In the source: [`Objects/typeobject.c:3431-3451@v3.15.0rc1#mro_implementation_unlocked`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3431-L3451).

### C3 linearization

**The merge rule that turns a class and its bases into one ordered list.**

Take the MRO of each base, add the declared bases tuple, and repeatedly take the first head that does not appear later in any of the lists. If no such head exists the merge fails and you get a TypeError instead of a class. CPython spells this out in `pmerge`, and it is about forty lines. The rule guarantees a class comes before its bases and that the order you declared bases in is preserved.

Also written C3, the merge. First met in O04. See also [MRO](#mro). In the source: [`Objects/typeobject.c:3361-3400@v3.15.0rc1#pmerge`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3361-L3400).

### static type

**A type object written out as a C literal and compiled into the binary.**
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ A lesson can also end with a boss fight, which is a problem the text does not so
| O01 | [The header, byte by byte](lessons/o01-the-header-byte-by-byte/o01.ipynb) | The two fields everybody quotes, read out of your own interpreter with ctypes, and the first of them turning out to be three: a thirty two bit count, sixteen bits named and unused, and the flags that say whether an object can ever be freed, plus why an immortal starts in the middle of the range and where a length lives when a type has one | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o01-the-header-byte-by-byte/o01.ipynb) |
| O02 | [Following the type pointer](lessons/o02-following-the-type-pointer/o02.ipynb) | The biggest struct in the interpreter, read from Python: what basicsize and itemsize say about an instance and why list breaks the arithmetic, the one flag bit behind cannot set attribute of immutable type, the minus one in tp_dictoffset that is a sentinel rather than an offset, the thirty two bytes reserved in front of every instance of a class you wrote, and the five steps a class statement actually compiles to | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o02-following-the-type-pointer/o02.ipynb) |
| O03 | [Dunders and the slots behind them](lessons/o03-dunders-and-slots/o03.ipynb) | Where int.__add__ comes from when nobody wrote it, why setting __repr__ on an instance does nothing and setting it on a class works an hour later, how one __getitem__ makes a class iterable without an __iter__, why a subclass __radd__ beats a base class __add__, and the three steps between defining __eq__ and getting unhashable type | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o03-dunders-and-slots/o03.ipynb) |
| O04 | [The order things are found in](lessons/o04-the-order-things-are-found-in/o04.ipynb) | Why a class with two bases carries a flat list instead of searching, the twenty line merge that builds it and matches CPython exactly on classes out of the standard library, the two ways a class statement can fail before the class exists, why super goes somewhere the class it is written in has never heard of, and the two ways the list changes after the fact | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o04-the-order-things-are-found-in/o04.ipynb) |

More are landing in order. [lessons/README.md](lessons/README.md) explains how one is put together and how to run them locally.

Expand Down
60 changes: 60 additions & 0 deletions citations.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -740,11 +740,71 @@
"first_line": "add_operators(PyTypeObject *type)",
"lines": 15
},
"Objects/typeobject.c:12586-12614@v3.15.0rc1": {
"digest": "965f0e77a7064b66",
"first_line": "PyObject *",
"lines": 29
},
"Objects/typeobject.c:1278-1298@v3.15.0rc1": {
"digest": "e2e21d98952ebb7b",
"first_line": "static void",
"lines": 21
},
"Objects/typeobject.c:1798-1810@v3.15.0rc1": {
"digest": "d8630aee772bf39e",
"first_line": "mro_hierarchy_for_complete_type(PyTypeObject *type, PyObject *temp)",
"lines": 13
},
"Objects/typeobject.c:2512-2531@v3.15.0rc1": {
"digest": "9ac3593478808a3d",
"first_line": "_PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems)",
"lines": 20
},
"Objects/typeobject.c:3246-3256@v3.15.0rc1": {
"digest": "73c31df6c43bee0c",
"first_line": "tail_contains(PyObject *tuple, Py_ssize_t whence, PyObject *o)",
"lines": 11
},
"Objects/typeobject.c:3270-3298@v3.15.0rc1": {
"digest": "2ec88f6f44bd8a6a",
"first_line": "check_duplicates(PyObject *tuple)",
"lines": 29
},
"Objects/typeobject.c:3309-3331@v3.15.0rc1": {
"digest": "2bb1be8360636ab0",
"first_line": "static void",
"lines": 23
},
"Objects/typeobject.c:3361-3400@v3.15.0rc1": {
"digest": "913155b71541437c",
"first_line": "pmerge(PyObject *acc, PyObject **to_merge, Py_ssize_t to_merge_size)",
"lines": 40
},
"Objects/typeobject.c:3431-3451@v3.15.0rc1": {
"digest": "087ba3b7f6fbf993",
"first_line": "mro_implementation_unlocked(PyTypeObject *type)",
"lines": 21
},
"Objects/typeobject.c:3453-3473@v3.15.0rc1": {
"digest": "7b880cb6bfeaf0a1",
"first_line": "if (n == 1) {",
"lines": 21
},
"Objects/typeobject.c:3480-3510@v3.15.0rc1": {
"digest": "8cb3a4a8df0d5132",
"first_line": "/* Find a superclass linearization that honors the constraints",
"lines": 31
},
"Objects/typeobject.c:3590-3609@v3.15.0rc1": {
"digest": "31f3540815b1fe10",
"first_line": "static PyObject *",
"lines": 20
},
"Objects/typeobject.c:6144-6180@v3.15.0rc1": {
"digest": "a16a8c9e914d75f2",
"first_line": "static int",
"lines": 37
},
"Objects/typeobject.c:7290-7295@v3.15.0rc1": {
"digest": "e3fd1c7921a386a3",
"first_line": "PyTypeObject PyType_Type = {",
Expand Down
16 changes: 15 additions & 1 deletion lessons/CLAIMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ header, what the allocator does with a freed block, the shape of the eval loop.
marked with the reason, and a lesson is allowed at most 3 of them. The cap is the point.
Without it the exception becomes the rule and this goes back to being a book.

263 claims across 31 lessons, 21 of them not observable from Python.
272 claims across 32 lessons, 21 of them not observable from Python.

## B01. Building CPython, and whether you need to

Expand Down Expand Up @@ -218,6 +218,20 @@ Without it the exception becomes the rule and this goes back to being a book.
| a subclass that defines __radd__ itself is called before the base class __add__, and an unrelated class defining __radd__ is not, because both names share one slot and the dispatcher checks the subclass relationship first | [`o03-17`](o03-dunders-and-slots/o03.ipynb) |
| a class that defines __eq__ and not __hash__ ends up with a real None stored under __hash__ in its class dict, and putting a hash back is a one line assignment | [`o03-19`](o03-dunders-and-slots/o03.ipynb) |

## O04. The order things are found in

| Claim | Proved by |
| --- | --- |
| a class with two bases carries a flat __mro__ tuple that flattens both paths into one order, and method lookup follows that order rather than searching the base classes | [`o04-07`](o04-the-order-things-are-found-in/o04.ipynb) |
| swapping the order of the declared bases changes the resulting MRO, so the bases tuple is an input to the computation and not just a record of what you typed | [`o04-09`](o04-the-order-things-are-found-in/o04.ipynb) |
| every class's MRO starts with the class itself, ends with object, and lists each declared base in the order it was declared | [`o04-11`](o04-the-order-things-are-found-in/o04.ipynb) |
| a twenty line C3 merge written in Python reproduces CPython's __mro__ exactly, for hand written diamonds and for classes taken from the standard library | [`o04-13`](o04-the-order-things-are-found-in/o04.ipynb) |
| a chain of single inheritance produces an MRO that is just each class prepended to its base's MRO, which is the fast path CPython takes without running the merge | [`o04-15`](o04-the-order-things-are-found-in/o04.ipynb) |
| a pair of bases that order two classes in opposite ways makes the class statement itself raise TypeError, and a repeated base is rejected earlier by a separate check with a different message | [`o04-17`](o04-the-order-things-are-found-in/o04.ipynb) |
| super in a method resolves against the MRO of the instance's type, so an unchanged method in Left can dispatch to Right when the instance is a Both, even though Left never refers to Right | [`o04-19`](o04-the-order-things-are-found-in/o04.ipynb) |
| assigning to a class's __bases__ recomputes the MRO of that class and of every subclass, and instances that already exist pick up the new method at once | [`o04-22`](o04-the-order-things-are-found-in/o04.ipynb) |
| a metaclass that overrides mro can return an order the C3 rules would never produce, and attribute lookup uses that order without complaint | [`o04-24`](o04-the-order-things-are-found-in/o04.ipynb) |

## T01. One line, seven stages

| Claim | Proved by |
Expand Down
Loading
Loading