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) | [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) | [data descriptor](#data-descriptor) | [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) | [bound method](#bound-method) | [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) | [data descriptor](#data-descriptor) | [deallocation](#deallocation) | [debug build](#debug-build) | [deoptimization](#deoptimization) | [descriptor](#descriptor) | [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).

### descriptor

**An object in a class dict whose type defines __get__, __set__ or __delete__.**

Reading an attribute that resolves to a descriptor calls `__get__` rather than handing the object back. There is nothing to inherit from and nothing to register, so having the method is the whole qualification. Functions are descriptors, which is where `self` comes from: `func_descr_get` returns the function on a class and a bound method on an instance. `property`, `classmethod`, `staticmethod`, every `__slots__` entry and most attributes defined from C are descriptors too. The protocol only applies to objects found on the type, so a descriptor sitting in an instance dict is an ordinary value.

Also written descriptor protocol. First met in O06. See also [data descriptor](#data-descriptor), [slot](#slot). In the source: [`Objects/funcobject.c:1264-1270@v3.15.0rc1#func_descr_get`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/funcobject.c#L1264-L1270).

### bound method

**A small object holding a function and the instance it was read from.**

`PyMethod_New` allocates one with two pointers, `im_func` and `im_self`, and calling it inserts the instance as the first argument. That is all `self` is. A fresh one is built on every attribute read, so `obj.method is obj.method` is false, though the two compare equal. The allocation comes off a free list when one is available, and the interpreter specialises the common call shape so that reading and immediately calling a method skips building the object at all.

First met in O06. See also [descriptor](#descriptor), [type object](#type-object). In the source: [`Objects/classobject.c:64-84@v3.15.0rc1#PyMethod_New`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/classobject.c#L64-L84).

### data descriptor

**An object on a type that has __set__ or __delete__ as well as __get__.**
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ A lesson can also end with a boss fight, which is a problem the text does not so
| 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) |
| O05 | [What a dot does](lessons/o05-what-a-dot-does/o05.ipynb) | The four places x.name looks and the fixed order it looks in them, why a property cannot be shadowed by an instance attribute and a method can, why __getattr__ and __getattribute__ are not two versions of the same hook, why a class lookup starts at the metaclass and passes None where the instance would go, and the version tag that lets four thousand cache entries go stale without anybody clearing them | 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/o05-what-a-dot-does/o05.ipynb) |
| O06 | [What property actually is](lessons/o06-what-property-actually-is/o06.ipynb) | The three method protocol that property, classmethod, staticmethod and the self in every method you have written all turn out to be, the one line of C that decides whether a descriptor beats the instance dict or loses to it, the seven lines that build a bound method, the five descriptor types C code makes because it cannot write a class body, why a descriptor in an instance dict does nothing at all, and the pass over the class dict that tells each descriptor the name it was assigned 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/o06-what-property-actually-is/o06.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
70 changes: 70 additions & 0 deletions citations.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,11 @@
"first_line": "PyNumber_Multiply(PyObject *v, PyObject *w)",
"lines": 1
},
"Objects/classobject.c:64-84@v3.15.0rc1": {
"digest": "bb82668b374833eb",
"first_line": "PyObject *",
"lines": 21
},
"Objects/clinic/listobject.c.h:1-3@v3.15.0rc1": {
"digest": "3e8ec84ffbf9f1e1",
"first_line": "/*[clinic input]",
Expand Down Expand Up @@ -645,6 +650,66 @@
"first_line": "int",
"lines": 5
},
"Objects/descrobject.c:137-160@v3.15.0rc1": {
"digest": "1026e33b396ad845",
"first_line": "static PyObject *",
"lines": 24
},
"Objects/descrobject.c:163-181@v3.15.0rc1": {
"digest": "822311e554d7bab8",
"first_line": "member_get(PyObject *self, PyObject *obj, PyObject *type)",
"lines": 19
},
"Objects/descrobject.c:1685-1718@v3.15.0rc1": {
"digest": "b15e3a6673c9087b",
"first_line": "property_descr_get(PyObject *self, PyObject *obj, PyObject *type)",
"lines": 34
},
"Objects/descrobject.c:1720-1755@v3.15.0rc1": {
"digest": "fe21cc3e01f1ce44",
"first_line": "static int",
"lines": 36
},
"Objects/descrobject.c:183-201@v3.15.0rc1": {
"digest": "135f0037da281aac",
"first_line": "static PyObject *",
"lines": 19
},
"Objects/descrobject.c:203-214@v3.15.0rc1": {
"digest": "f4d5b429346acb28",
"first_line": "static PyObject *",
"lines": 12
},
"Objects/descrobject.c:216-240@v3.15.0rc1": {
"digest": "02bee99884323ef9",
"first_line": "static int",
"lines": 25
},
"Objects/descrobject.c:794-828@v3.15.0rc1": {
"digest": "f10efe24da69b92e",
"first_line": "PyTypeObject PyMemberDescr_Type = {",
"lines": 35
},
"Objects/descrobject.c:94-130@v3.15.0rc1": {
"digest": "bc66e218dea859cc",
"first_line": "static PyObject *",
"lines": 37
},
"Objects/funcobject.c:1264-1270@v3.15.0rc1": {
"digest": "991dc84999f49f93",
"first_line": "func_descr_get(PyObject *func, PyObject *obj, PyObject *type)",
"lines": 7
},
"Objects/funcobject.c:1530-1537@v3.15.0rc1": {
"digest": "760d452e18210a9c",
"first_line": "static PyObject *",
"lines": 8
},
"Objects/funcobject.c:1794-1799@v3.15.0rc1": {
"digest": "edc1441a5461c263",
"first_line": "static PyObject *",
"lines": 6
},
"Objects/listobject.c:103-134@v3.15.0rc1": {
"digest": "779a7953df231c11",
"first_line": "static int",
Expand Down Expand Up @@ -775,6 +840,11 @@
"first_line": "dict. */",
"lines": 10
},
"Objects/typeobject.c:12274-12308@v3.15.0rc1": {
"digest": "221dabb2a1f5ee99",
"first_line": "static int",
"lines": 35
},
"Objects/typeobject.c:12456-12470@v3.15.0rc1": {
"digest": "b3eb73a5e54ec845",
"first_line": "add_operators(PyTypeObject *type)",
Expand Down
13 changes: 12 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.

281 claims across 33 lessons, 22 of them not observable from Python.
287 claims across 34 lessons, 22 of them not observable from Python.

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

Expand Down Expand Up @@ -246,6 +246,17 @@ Without it the exception becomes the rule and this goes back to being a book.
| the same LOAD_ATTR instruction takes a different specialised form depending on whether the attribute lives in the instance values, in a slot, on a module, or is a method | [`o05-20`](o05-what-a-dot-does/o05.ipynb) |
| assigning a property over an attribute the specialised instruction was relying on makes the guard fail, and the instruction goes back to the general form and then settles on a different specialised one | [`o05-22`](o05-what-a-dot-does/o05.ipynb) |

## O06. What property actually is

| Claim | Proved by |
| --- | --- |
| an object with __get__ and __set__ in a class dict intercepts both reads and writes of that attribute on every instance of the class | [`o06-07`](o06-what-property-actually-is/o06.ipynb) |
| reading a method off an instance calls the function's __get__ and produces a new bound method object each time, while reading it off the class returns the plain function | [`o06-10`](o06-what-property-actually-is/o06.ipynb) |
| classmethod produces a bound method whose __self__ is the class, and the class it binds to is the one the lookup started from rather than the one that defined the method | [`o06-14`](o06-what-property-actually-is/o06.ipynb) |
| attributes defined from C appear as one of five descriptor types, and whether each is a data descriptor is decided by whether its type fills in tp_descr_set | [`o06-20`](o06-what-property-actually-is/o06.ipynb) |
| a descriptor placed in an instance dict is returned as itself, because the lookup that would call __get__ only runs for objects found on the type | [`o06-24`](o06-what-property-actually-is/o06.ipynb) |
| __set_name__ runs once for every entry in the class dict during class creation, and never runs again for later assignments to the class | [`o06-26`](o06-what-property-actually-is/o06.ipynb) |

## T01. One line, seven stages

| Claim | Proved by |
Expand Down
Loading
Loading