diff --git a/GLOSSARY.md b/GLOSSARY.md index 151b33e..7f2c391 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -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) | [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) | [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) ## Reading the source @@ -658,6 +658,14 @@ 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). +### data descriptor + +**An object on a type that has __set__ or __delete__ as well as __get__.** + +The test is one line: `PyDescr_IsData` asks whether `tp_descr_set` is filled in. That one bit decides the whole precedence question, because attribute lookup calls a data descriptor before it ever looks in the instance dict and calls anything else after. `property` and the descriptors `__slots__` generates are data descriptors. A plain function is not, which is why you can shadow a method on one instance and cannot shadow a property. + +Also written non data descriptor. First met in O05. See also [slot](#slot), [type object](#type-object). In the source: [`Objects/descrobject.c:1028-1032@v3.15.0rc1#PyDescr_IsData`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L1028-L1032). + ### MRO **The flat list of classes, in order, that a name is looked up in.** diff --git a/README.md b/README.md index 4fdc519..80cf6d5 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ A lesson can also end with a boss fight, which is a problem the text does not so | 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) | +| 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) | More are landing in order. [lessons/README.md](lessons/README.md) explains how one is put together and how to run them locally. diff --git a/citations.lock.json b/citations.lock.json index e45076a..f7fb111 100644 --- a/citations.lock.json +++ b/citations.lock.json @@ -640,6 +640,11 @@ "first_line": "PyCodeObject *", "lines": 4 }, + "Objects/descrobject.c:1028-1032@v3.15.0rc1": { + "digest": "0bdba2c917b75359", + "first_line": "int", + "lines": 5 + }, "Objects/listobject.c:103-134@v3.15.0rc1": { "digest": "779a7953df231c11", "first_line": "static int", @@ -675,6 +680,26 @@ "first_line": "_PyObject_ComputedDictPointer(PyObject *obj)", "lines": 23 }, + "Objects/object.c:1887-1926@v3.15.0rc1": { + "digest": "e43f250aaa47532f", + "first_line": "_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,", + "lines": 40 + }, + "Objects/object.c:1964-1979@v3.15.0rc1": { + "digest": "c36e1e221a8cd787", + "first_line": "if (dict != NULL) {", + "lines": 16 + }, + "Objects/object.c:1981-2002@v3.15.0rc1": { + "digest": "f2770e63b5eb1bdf", + "first_line": "if (f != NULL) {", + "lines": 22 + }, + "Objects/object.c:1996-2002@v3.15.0rc1": { + "digest": "aa9e41e294ee44d7", + "first_line": "if (!suppress) {", + "lines": 7 + }, "Objects/object.c:2773-2791@v3.15.0rc1": { "digest": "84c3b29a29ccb125", "first_line": "void", @@ -705,6 +730,16 @@ "first_line": "#define SLOT1BINFULL(FUNCNAME, TESTFUNC, SLOTNAME, DUNDER, RDUNDER) \\", "lines": 28 }, + "Objects/typeobject.c:10990-11026@v3.15.0rc1": { + "digest": "c607830bbd258217", + "first_line": "PyObject *", + "lines": 37 + }, + "Objects/typeobject.c:11002-11009@v3.15.0rc1": { + "digest": "6081f882f79f8ec6", + "first_line": "if (getattr == NULL) {", + "lines": 8 + }, "Objects/typeobject.c:11522-11531@v3.15.0rc1": { "digest": "4ad77fa864aa2cae", "first_line": "/*", @@ -720,6 +755,11 @@ "first_line": "static pytype_slotdef slotdefs[] = {", "lines": 7 }, + "Objects/typeobject.c:1166-1195@v3.15.0rc1": { + "digest": "f65ccc798f6a3cbd", + "first_line": "type_modified_unlocked(PyTypeObject *type)", + "lines": 30 + }, "Objects/typeobject.c:11740-11753@v3.15.0rc1": { "digest": "77bf33636ec9ffc9", "first_line": "MPSLOT(__len__, mp_length, slot_mp_length, wrap_lenfunc,", @@ -750,6 +790,11 @@ "first_line": "static void", "lines": 21 }, + "Objects/typeobject.c:1389-1392@v3.15.0rc1": { + "digest": "7ed19048d6bc13f5", + "first_line": "#define MAX_VERSIONS_PER_CLASS 1000", + "lines": 4 + }, "Objects/typeobject.c:1798-1810@v3.15.0rc1": { "digest": "d8630aee772bf39e", "first_line": "mro_hierarchy_for_complete_type(PyTypeObject *type, PyObject *temp)", @@ -800,11 +845,36 @@ "first_line": "static PyObject *", "lines": 20 }, + "Objects/typeobject.c:44-58@v3.15.0rc1": { + "digest": "f718a74b28ee2783", + "first_line": "/* The cache can keep references to the names alive for longer than", + "lines": 15 + }, "Objects/typeobject.c:6144-6180@v3.15.0rc1": { "digest": "a16a8c9e914d75f2", "first_line": "static int", "lines": 37 }, + "Objects/typeobject.c:6306-6345@v3.15.0rc1": { + "digest": "26beeb64230a5866", + "first_line": "_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out)", + "lines": 40 + }, + "Objects/typeobject.c:6570-6600@v3.15.0rc1": { + "digest": "4e8db6ed47e730d6", + "first_line": "_PyStackRef", + "lines": 31 + }, + "Objects/typeobject.c:6600-6616@v3.15.0rc1": { + "digest": "7f7ce8c804fe44d1", + "first_line": "if (!PyStackRef_IsNull(meta_attribute_ref.ref)) {", + "lines": 17 + }, + "Objects/typeobject.c:6618-6650@v3.15.0rc1": { + "digest": "601da348b9cd58a9", + "first_line": "/* No data descriptor found on metatype. Look in tp_dict of this", + "lines": 33 + }, "Objects/typeobject.c:7290-7295@v3.15.0rc1": { "digest": "e3fd1c7921a386a3", "first_line": "PyTypeObject PyType_Type = {", diff --git a/lessons/CLAIMS.md b/lessons/CLAIMS.md index 115dd24..346022b 100644 --- a/lessons/CLAIMS.md +++ b/lessons/CLAIMS.md @@ -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. -272 claims across 32 lessons, 21 of them not observable from Python. +281 claims across 33 lessons, 22 of them not observable from Python. ## B01. Building CPython, and whether you need to @@ -232,6 +232,20 @@ Without it the exception becomes the rule and this goes back to being a book. | 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) | +## O05. What a dot does + +| Claim | Proved by | +| --- | --- | +| with the same name present on the type and in the instance dict, a data descriptor on the type wins and a non data descriptor loses, and the instance dict entry is untouched either way | [`o05-07`](o05-what-a-dot-does/o05.ipynb) | +| __getattr__ runs only for names the normal lookup failed to find, while an overridden __getattribute__ runs for every name including __dict__ | [`o05-09`](o05-what-a-dot-does/o05.ipynb) | +| assigning __getattr__ to an instance leaves it in the instance dict and has no effect on attribute lookup | [`o05-11`](o05-what-a-dot-does/o05.ipynb) | +| a name defined on the metaclass is reachable through the class and not through an instance of it, and a descriptor found on a class is called with None where the instance would be | [`o05-13`](o05-what-a-dot-does/o05.ipynb) | +| the AttributeError for an instance and for a class have different wording, and the exception carries the name and the object as attributes | [`o05-15`](o05-what-a-dot-does/o05.ipynb) | +| the did you mean suggestion is not part of the exception message and only appears when a traceback is formatted, because it is computed from the obj attribute at that point | [`o05-17`](o05-what-a-dot-does/o05.ipynb) | +| the cache and the version tag are not visible from Python at all, since nothing exposes tp_version_tag, the hit rate, or the cache contents | not observable from Python: the effect is only measurable as a timing difference, and the specialised bytecode in the next section is the closest observable proxy | +| 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) | + ## T01. One line, seven stages | Claim | Proved by | diff --git a/lessons/README.md b/lessons/README.md index 88462b3..ee46bc5 100644 --- a/lessons/README.md +++ b/lessons/README.md @@ -36,6 +36,7 @@ Each lesson is a notebook you can run. There is nothing to install and nothing t | [O02. Following the type pointer](o02-following-the-type-pointer/o02.ipynb) | Where the second word of an object goes, why type is its own type, the two numbers a type keeps about how big its instances are and the one type they do not predict, the flag bit that decides whether you can assign to a type, the negative offsets that are not offsets and the space reserved in front of every instance, and the class statement turning out to be an ordinary function call | [![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](o03-dunders-and-slots/o03.ipynb) | The table of ninety four rows that connects every dunder name to a C slot, read forwards when you write a class and backwards when a type written in C is made ready, why a dunder set on an instance is ignored and one assigned to a class takes effect at once, the names that fill two slots and give you truthiness and iteration for free, the two names that share one slot and have to argue about who goes first, and the two mechanisms that put a literal None in __hash__ | [![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](o04-the-order-things-are-found-in/o04.ipynb) | The MRO as a list computed once and stored on the type rather than a search done every time, the C3 merge written out in twenty lines of Python and checked against __mro__ for classes from this notebook and from the standard library, the fast path that skips the merge entirely for single inheritance, the two error messages you get when no order exists and when a base is repeated, why super reads the MRO of the instance and not of the class the call was written in, and how assigning __bases__ or writing a metaclass mro changes the answer afterwards | [![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](o05-what-a-dot-does/o05.ipynb) | PyObject_GenericGetAttr in the order it actually runs, the one if statement that is the whole of the precedence rule people memorise, the two attribute hooks that differ by five characters and by everything else, the separate function a class lookup goes through and the None it passes in place of an instance, where the did you mean suggestion is really computed, the per interpreter cache keyed on a version number that gets zeroed rather than swept, and the four different instructions the same line of Python turns into once it has run enough times | [![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) | ## The three programs diff --git a/lessons/o05-what-a-dot-does/build.py b/lessons/o05-what-a-dot-does/build.py new file mode 100644 index 0000000..25d2a06 --- /dev/null +++ b/lessons/o05-what-a-dot-does/build.py @@ -0,0 +1,491 @@ +#!/usr/bin/env python +"""O05. What a dot does. + +The fifth lesson of the object model part. O04 built the list of classes a name gets looked up +in. This one is about the function that reads that list, and about the three other places it +looks before and after. + +`PyObject_GenericGetAttr` is a hundred and twenty lines and it explains almost everything people +learn about attributes as separate rules. Why a property cannot be shadowed by an instance +attribute. Why a method can. Why `__getattr__` and `__getattribute__` are not variants of each +other. Why looking a name up on a class takes a different function entirely and passes `None` +where the instance would go. + +Then there is the part that makes it fast, which is a per interpreter cache keyed on a version +number that gets zeroed whenever anything about a class changes, and the bytecode rewriting +itself around what the cache keeps saying. + +Run this file to regenerate the notebook, or `just build-lessons` to regenerate all of them. +`just lessons` checks that the committed notebook still matches this file. +""" + +from nbbuild import BANNER, Lesson +from nbdiagram import Diagrams + +lesson = Lesson("o05-what-a-dot-does", "o05") +badge = lesson.badge +cite = lesson.cite +term = lesson.term +figure = Diagrams("o05-what-a-dot-does").figure + + +lesson.md(f""" +# O05. What a dot does + +{badge} + +O04 finished with a four line loop: walk the MRO, do a dict lookup in each class, stop at the first hit. That loop is real, but it is only one of four things that happen when you write `x.name`, and it is not the first. + +{figure("five-steps-in-order", "the four places a lookup checks, in the fixed order it checks them")} + +Everything people learn about attributes as a separate rule is this one order. A property cannot be shadowed by an instance attribute, and a method can. `__getattr__` runs sometimes and `__getattribute__` runs always. Looking a name up on a class behaves differently from looking it up on an instance. Four rules, one function. +""") + + +lesson.md(""" +## About the source references + +Now and then this lesson points at CPython's own source, like this: `Objects/object.c:1887-1926@v3.15.0rc1#_PyObject_GenericGetAttrWithDict`. + +Read it as three parts: the file, the lines, and the release those line numbers belong to. Sometimes there is a fourth part after a `#`, which is the name of the thing those lines are inside. + +Every reference is a link, and every one is checked against the pinned source on each change, so a stale reference fails the build instead of sending you somewhere wrong. You never have to read any of it. The references are there so you can go deeper when you want to, and so you can check that this lesson is not making things up. + +## Setup + +Colab does not come with the small package these lessons use, so the next cell installs it. If you are running this from a checkout of the repository it is already installed and the cell does nothing. +""") + + +lesson.code(""" +import sys + +if sys.version_info < (3, 14): + print("This lesson needs CPython 3.14 or newer.") + print(f"This runtime is {sys.version.split()[0]}, and the cells below will not run on it.") +else: + try: + import pyxray + except ImportError: + %pip install -q "pyxray @ git+https://github.com/tamnd/cpython-internals@main#subdirectory=pyxray" + import pyxray +""") + + +lesson.md(""" +## Which Python is this + +Everything below was checked against the version this cell prints and against 3.14. One cell has a different wording on the two, and it says so. +""") + + +lesson.code( + """ +import pyxray + +pyxray.show() +""", + differs=BANNER, + quiet=True, +) + + +lesson.md(f""" +## Four places, one order + +The function is `_PyObject_GenericGetAttrWithDict`, {cite("Objects/object.c:1887-1926@v3.15.0rc1#_PyObject_GenericGetAttrWithDict")}, and `PyObject_GenericGetAttr` is a one line wrapper around it. Read past the error checking and it does four things in this order. + +One: walk the type's MRO for the name. That is O04's loop, and the result is called `descr` for the rest of the function. + +Two: if what came back has a `__get__` and also a `__set__`, call `__get__` right there and return. Something with both is a {term("data descriptor")}, and this branch is the only reason a property wins. + +Three: otherwise look in the instance dict, and return that if it is there, {cite("Objects/object.c:1964-1979@v3.15.0rc1")}. + +Four: otherwise fall back on what the MRO found. If it has a `__get__`, call it. If it is a plain value, return it. If there was nothing at all, raise `AttributeError`, {cite("Objects/object.c:1981-2002@v3.15.0rc1")}. + +{figure("who-wins", "which of the type and the instance dict wins, for each kind of thing on the type")} + +So the type is consulted first but usually does not win, because step two only fires for a data descriptor. The whole of the famous precedence rule is the position of one `if`. + +{lesson.claim("with the same name present on the type and in the instance dict, a data descriptor on the type wins and a non data descriptor loses, and the instance dict entry is untouched either way")} +""") + + +lesson.code( + """ +class NonData: + def __get__(self, obj, owner): + return "the non data descriptor on the type" + + +class Data: + def __get__(self, obj, owner): + return "the data descriptor on the type" + + def __set__(self, obj, value): + pass + + +class Sample: + plain = "a plain class attribute" + nondata = NonData() + data = Data() + + +s = Sample() +for name in ["data", "plain", "nondata"]: + s.__dict__[name] = "the instance dict" + +for name in ["data", "plain", "nondata"]: + print(f" s.{name:8} {getattr(s, name)}") + print(f" and s.__dict__[{name!r}] is still {s.__dict__[name]!r}") +""" +) + + +lesson.md(f""" +The instance dict entry for `data` is right there and the lookup ignored it. That is not a special case for properties, it is step two firing before step three. + +This also explains something that sounds unrelated. A method is a function, and a function has `__get__` but no `__set__`, so it is a non data descriptor and step three beats it. That is why `obj.method = something` works and shadows the method for that one object, while `obj.some_property = something` does not shadow anything and instead calls the property's setter. + +## Two hooks that are not the same hook + +`__getattribute__` and `__getattr__` differ by five characters and by almost everything else. + +{figure("two-hooks-not-one", "when each of the two hooks runs and what happens when it does not")} + +`__getattribute__` is the slot. Everything above is the default implementation of it. Override it and you replace the whole four step function, for every attribute, including `__dict__` and `__class__`. + +`__getattr__` is not a slot at all. There is no default. It is a name the slot dispatcher looks for, and if a class has one, `update_one_slot` installs a different dispatcher called `_Py_slot_tp_getattr_hook`, {cite("Objects/typeobject.c:10990-11026@v3.15.0rc1#_Py_slot_tp_getattr_hook")}. That one runs the normal lookup with errors suppressed, and only calls your `__getattr__` if the normal lookup came back empty. + +There is a small piece of housekeeping worth seeing in that function. If a class has no `__getattr__`, the hook dispatcher swaps itself out of the slot for the simpler one and never runs again, {cite("Objects/typeobject.c:11002-11009@v3.15.0rc1")}. + +{lesson.claim("__getattr__ runs only for names the normal lookup failed to find, while an overridden __getattribute__ runs for every name including __dict__")} +""") + + +lesson.code( + """ +class Fallback: + on_the_class = 1 + + def __getattr__(self, name): + return f"__getattr__ made up {name!r}" + + +f = Fallback() +f.on_the_instance = 2 + +print(f" f.on_the_class {f.on_the_class}") +print(f" f.on_the_instance {f.on_the_instance}") +print(f" f.missing {f.missing}") + + +class Every: + def __getattribute__(self, name): + return f"__getattribute__ saw {name!r}" + + def __getattr__(self, name): + return "this is never reached" + + +e = Every() +e.here = "this really is in the instance dict" + +print() +print(f" e.here {e.here}") +print(f" e.missing {e.missing}") +print(f" e.__dict__ {e.__dict__}") +""" +) + + +lesson.md(f""" +The last line is the one to sit with. `e.__dict__` went through `__getattribute__` too, so there is now no ordinary way to see what is actually in that object. `object.__getattribute__(e, "__dict__")` still works, because that calls the C function directly instead of going through the type. + +Both hooks live on the type, like every other dunder in O03, so setting `__getattr__` on an instance does nothing at all. + +{lesson.claim("assigning __getattr__ to an instance leaves it in the instance dict and has no effect on attribute lookup")} +""") + + +lesson.code( + """ +class Quiet: + pass + + +q = Quiet() +q.__getattr__ = lambda name: "set on the instance" + +print(f" it is in the instance dict {'__getattr__' in q.__dict__}") +try: + value = q.missing +except AttributeError as error: + print(f" and q.missing still raises AttributeError: {error}") +""" +) + + +lesson.md(f""" +## A class is looked up differently + +`Klass.name` does not run `PyObject_GenericGetAttr`. A type is an object whose type is `type`, so the slot that runs is `type`'s own `tp_getattro`, which is `_Py_type_getattro_stackref`, {cite("Objects/typeobject.c:6570-6600@v3.15.0rc1#_Py_type_getattro_stackref")}. + +{figure("a-class-is-different", "the metaclass MRO first, then the class's own MRO")} + +Same shape, one level up. It looks in the metaclass's MRO first, and if it finds a data descriptor there it calls it and stops, {cite("Objects/typeobject.c:6600-6616@v3.15.0rc1")}. Only then does it look in the class's own MRO, {cite("Objects/typeobject.c:6618-6650@v3.15.0rc1")}. + +There is one more difference and it is the one that matters in practice. When this function finds a descriptor on the class, it calls `__get__` with `NULL` for the instance, because there is no instance. That is the `None` you get as the second argument, and it is how a descriptor tells `Klass.thing` from `Klass().thing`. + +{lesson.claim("a name defined on the metaclass is reachable through the class and not through an instance of it, and a descriptor found on a class is called with None where the instance would be")} +""") + + +lesson.code( + """ +class Meta(type): + from_the_metaclass = "found on the metaclass" + + +class Klass(metaclass=Meta): + from_the_class = "found on the class" + + +print(f" Klass.from_the_class {Klass.from_the_class}") +print(f" Klass.from_the_metaclass {Klass.from_the_metaclass}") +try: + value = Klass().from_the_metaclass +except AttributeError as error: + print(f" Klass().from_the_metaclass AttributeError: {error}") + + +class Watcher: + def __get__(self, obj, owner): + return f"obj={obj!r} owner={owner.__name__}" + + +class Holder: + watched = Watcher() + + def __repr__(self): + return "a Holder" + + +print() +print(f" Holder.watched {Holder.watched}") +print(f" Holder().watched {Holder().watched}") +""" +) + + +lesson.md(f""" +The two error messages are different too, and now you know why: one comes out of the object function and one out of the type function. + +{lesson.claim("the AttributeError for an instance and for a class have different wording, and the exception carries the name and the object as attributes")} +""") + + +lesson.code( + """ +class Empty: + pass + + +for target, label in [(Empty(), "an instance"), (Empty, "the class")]: + try: + value = target.nope + except AttributeError as error: + print(f" {label:12} {error}") + + +class Config: + def __init__(self): + self.threshold = 1 + + +try: + value = Config().treshold +except AttributeError as error: + print() + print(f" error.name {error.name!r}") + print(f" error.obj is it {isinstance(error.obj, Config)}") + print(f" the message {error}") +""" +) + + +lesson.md(f""" +The `name` and `obj` attributes are not decoration. `_PyObject_SetAttributeErrorContext` attaches them on the way out, {cite("Objects/object.c:1996-2002@v3.15.0rc1")}, and the traceback machinery uses them later to look for a near miss among the attributes the object actually has. So the suggestion is not in the message. It is added at print time, out of `obj`, and it costs nothing unless the program is already on its way to a traceback. + +{lesson.claim("the did you mean suggestion is not part of the exception message and only appears when a traceback is formatted, because it is computed from the obj attribute at that point")} +""") + + +lesson.code( + """ +import traceback + +try: + value = Config().treshold +except AttributeError as error: + print(f" str(error) {error}") + print(f" the traceback {traceback.format_exc().splitlines()[-1]}") +""", + differs="On 3.14 the suggestion is worded `Did you mean: 'threshold'?`. 3.15 spells out both names with the dot in front, which reads better when the name is a long one.", +) + + +lesson.md(f""" +## How this is not slow + +Steps one and two happen on every single attribute access, and step one is a walk over the MRO doing a dict lookup per class. For `self.x` inside a loop that would be several dict lookups per iteration, which would be terrible. + +It is not, because of a cache. Each interpreter has one flat hash table of four thousand and ninety six entries, and each entry holds a type version, a name, and the answer, {cite("Objects/typeobject.c:6306-6345@v3.15.0rc1#_PyType_LookupStackRefAndVersion")}. + +{figure("the-version-tag", "the version tag, and what happens to it when a class changes")} + +The version is `tp_version_tag`, a number on the type. A hit needs both the version and the interned name pointer to match, so one comparison of two words replaces the whole walk. + +Invalidation is the elegant part. There is no pass over the cache looking for stale entries. `type_modified_unlocked` sets `tp_version_tag` to zero on the class and on every subclass, {cite("Objects/typeobject.c:1166-1195@v3.15.0rc1#type_modified_unlocked")}, and every entry that mentioned the old version simply stops matching. A type with a zero tag is never cached at all. + +There are two limits worth knowing. A name longer than a hundred characters is never cached, {cite("Objects/typeobject.c:44-58@v3.15.0rc1#MCACHE_CACHEABLE_NAME")}, and a class that has been modified a thousand times stops getting new version tags for good, {cite("Objects/typeobject.c:1389-1392@v3.15.0rc1#MAX_VERSIONS_PER_CLASS")}. The second one is why a class you keep reassigning attributes on gets permanently slower. + +{lesson.claim("the cache and the version tag are not visible from Python at all, since nothing exposes tp_version_tag, the hit rate, or the cache contents", unobservable="the effect is only measurable as a timing difference, and the specialised bytecode in the next section is the closest observable proxy")} + +## Where you can see it + +The cache is invisible, but the thing built on top of it is not. After a piece of bytecode has done the same kind of lookup a few times, the interpreter rewrites the instruction in place into a form that assumes the shape it keeps seeing. `dis` will show you the rewritten form if you ask for it. + +{figure("specialised-then-not", "the instruction changing form, failing its guard, and settling on a new one")} + +The guard on the specialised instruction is that same version tag, which is the connection. This is the very short version of a story that belongs to the interpreter part, but the attribute lookup case is the clearest one and it is right here. + +{lesson.claim("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")} +""") + + +lesson.code( + """ +import dis +import math + + +def attr_forms(func): + return [i.opname for i in dis.get_instructions(func, adaptive=True) if "LOAD_ATTR" in i.opname] + + +def show(func, label): + print(f" {label:32} {attr_forms(func)}") + + +class Point: + def __init__(self, x): + self.x = x + + def scaled(self): + return self.x * 2 + + +class Slotted: + __slots__ = ("x",) + + def __init__(self, x): + self.x = x + + +def read_value(point): + return point.x + + +def read_slot(point): + return point.x + + +def read_method(point): + return point.scaled() + + +def read_module(module): + return module.pi + + +point = Point(1) +show(read_value, "before it has ever run") +read_value(point) +show(read_value, "after one call") + +for _ in range(100): + read_value(point) + read_slot(Slotted(1)) + read_method(point) + read_module(math) + +show(read_value, "after a hundred") +show(read_slot, "a __slots__ class instead") +show(read_method, "a method rather than a value") +show(read_module, "an attribute of a module") +""" +) + + +lesson.md(f""" +Four different instructions, from four identical looking lines of Python. `LOAD_ATTR_INSTANCE_VALUE` reads a fixed offset in the inline values array with no dict lookup at all. `LOAD_ATTR_SLOT` reads a fixed offset in the object. `LOAD_ATTR_MODULE` skips straight to a module's dict. `LOAD_ATTR_METHOD_WITH_VALUES` fetches the function without building a bound method object. + +Now break the assumption and watch it recover. + +{lesson.claim("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")} +""") + + +lesson.code( + """ +show(read_value, "specialised on instance values") + +Point.x = property(lambda self: 99) +show(read_value, "right after the class changed") + +read_value(point) +show(read_value, "after running it once more") + +for _ in range(100): + read_value(point) +show(read_value, "and after a hundred more") + +print(f" and read_value(point) now gives {read_value(point)}") +""" +) + + +lesson.md(""" +The instruction did not change the moment the class did. It changed the next time it ran and its guard failed, and even then it went back to the general form first and waited a while before committing to the new shape. That backoff is deliberate: an object that keeps changing shape should not pay to re specialise on every access. + +## Try it yourself + +Three things to poke at. + +Write a class with `__getattribute__` that prints the name and then calls `object.__getattribute__` for the real answer, and use one of its instances normally for a few lines. The list of names that go through it is longer than you expect, and most of them are dunders you never wrote. + +Take the precedence table and add a fifth row: a data descriptor on the type whose `__get__` raises `AttributeError`. Then call `hasattr`. The lookup function suppresses that one exception and nothing else, which is why `hasattr` returns `False` there and propagates a `ValueError` from the same place. + +Time `point.x` on a plain class against the same attribute on a `__slots__` class, a thousand accesses each, using `timeit`. Then assign something new to the class in between and time it again. The numbers are the only view you get of the cache, and they move. + +## What just happened + +`x.name` is one function doing four things in a fixed order: walk the type's MRO, use what it found if that is a data descriptor, otherwise use the instance dict, otherwise fall back on what the MRO found and raise if there was nothing. + +Everything that gets taught as a precedence rule is the position of one `if` in that function. A property has `__set__`, so it wins. A function does not, so an instance attribute of the same name shadows the method. + +`__getattribute__` is the slot and runs for everything. `__getattr__` is not a slot, has no default, and only runs when the normal path came back empty. A class with one gets a different dispatcher installed, and that dispatcher removes itself when there is nothing to dispatch to. + +Looking a name up on a class runs a different function that checks the metaclass first and then the class, and calls any descriptor it finds with `None` where the instance would go. The two `AttributeError` messages are different because they come from different functions, and the exception carries the name and the object so a traceback can suggest a near miss. + +All of it is fast because of a four thousand entry cache keyed on a version number per type. Changing anything on a class zeroes that number on the class and every subclass, and every cached entry quietly stops matching. Nothing about the cache is visible from Python, but the specialised bytecode built on top of it is, and it takes a different form for instance values, slots, modules and methods. + +## What is next + +O06 is descriptors properly. This lesson used them as a thing that either has `__set__` or does not. The next one is about what they are: `property`, `classmethod`, `staticmethod` and the bound method you get from `obj.method` are all the same protocol, and three of the four are less than twenty lines of C. +""") + + +raise SystemExit(lesson.save()) diff --git a/lessons/o05-what-a-dot-does/diagrams.py b/lessons/o05-what-a-dot-does/diagrams.py new file mode 100644 index 0000000..116e689 --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +"""The diagrams for O05, what actually happens when you write a dot. + +Each scene is written out twice, as an editable `.excalidraw` and as the `.svg` the lesson +embeds. Run this file to regenerate them, or `just build-diagrams` for every lesson. + +The one carrying the lesson is `five-steps-in-order`. Everything people learn as separate +rules about attributes is one function reading four places in a fixed order, and knowing the +order is enough to predict every case. +""" + +from nbdiagram import Gallery, figures + +gallery = Gallery("o05-what-a-dot-does") + +gallery.add( + figures.flow( + "five-steps-in-order", + [ + "x.name, and tp_getattro gets called", + "walk type(x).__mro__ for name", + "found a data descriptor, call its __get__", + "otherwise look in the instance dict", + "otherwise use what the MRO found", + "otherwise raise AttributeError", + ], + title="What PyObject_GenericGetAttr does, in the order it does it", + labels=[ + "one lookup, cached, described in O04", + "one that has __set__ as well as __get__", + "which is why a property cannot be shadowed", + "a plain value, or a __get__ with no __set__", + ], + tones=["input", "focus", "warning", "focus", "focus", "quiet"], + ) +) + + +gallery.add( + figures.table( + "who-wins", + ["what is on the type", "what is in the instance dict", "what x.name gives you"], + [ + ["a data descriptor", "a value", "the descriptor"], + ["a non data descriptor", "a value", "the instance dict"], + ["a plain value", "a value", "the instance dict"], + ["a non data descriptor", "nothing", "the descriptor"], + ["nothing", "nothing", "AttributeError, then __getattr__"], + ], + title="The only row that surprises people is the first one", + caption="A data descriptor is one with __set__ or __delete__, which is what property and slots make.", + tones=["warning", "quiet", "quiet", "quiet", "focus"], + ) +) + + +gallery.add( + figures.compare( + "two-hooks-not-one", + ( + "__getattribute__", + [ + "runs for every single attribute", + "including __dict__ and __class__", + "the default one is the C function", + "override it and you own everything", + ], + ), + ( + "__getattr__", + [ + "runs only after the normal path failed", + "and only on AttributeError", + "there is no default one at all", + "override it and you own the gaps", + ], + ), + title="Two names one letter apart that do completely different jobs", + verdict="The slot is the same either way. A class with __getattr__ gets the hook dispatcher instead.", + verdict_tone="focus", + ) +) + + +gallery.add( + figures.flow( + "a-class-is-different", + [ + "Klass.name, and type_getattro gets called", + "walk type(Klass).__mro__, the metaclass chain", + "found a data descriptor there, call it", + "otherwise walk Klass.__mro__ itself", + "and call __get__ with None as the instance", + ], + title="Looking a name up on a class takes a different function", + labels=[ + "the metaclass comes first, not the class", + "this is how a metaclass property works", + "the same MRO walk, one level down", + ], + tones=["input", "focus", "warning", "focus", "durable"], + ) +) + + +gallery.add( + figures.stack( + "the-version-tag", + [ + "every type has a tp_version_tag, a plain number", + "the cache stores type version, name, and the answer", + "a hit needs both the version and the name to match", + "changing anything on the class zeroes the tag", + "and zeroes it on every subclass too", + ], + title="How the answer gets remembered, and how it gets forgotten", + note="There is no invalidation pass. The old entries stay and simply never match again.", + ) +) + + +gallery.add( + figures.flow( + "specialised-then-not", + [ + "LOAD_ATTR, the general form", + "after a few runs, LOAD_ATTR_INSTANCE_VALUE", + "someone assigns a property to the class", + "the guard fails and it goes back to general", + "and settles on LOAD_ATTR_PROPERTY", + ], + title="The bytecode rewrites itself around what it keeps seeing", + labels=[ + "the guard is the type version tag", + "which zeroes every version tag under it", + "no invalidation pass, just a check that stops matching", + ], + tones=["input", "focus", "warning", "intermediate", "durable"], + ) +) + + +raise SystemExit(gallery.save()) diff --git a/lessons/o05-what-a-dot-does/diagrams/a-class-is-different.excalidraw b/lessons/o05-what-a-dot-does/diagrams/a-class-is-different.excalidraw new file mode 100644 index 0000000..59f4830 --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/a-class-is-different.excalidraw @@ -0,0 +1,746 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "40e441c2895ea2bd783e", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 741.24, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "Looking a name up on a class takes a different function", + "originalText": "Looking a name up on a class takes a different function", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d3e8330a405ab419a8e1", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 539.5999999999999, + "height": 70.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "c61ace28e5acd960524e", + "type": "text" + }, + { + "id": "a68aeb956196a7265ff8", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "c61ace28e5acd960524e", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 507.5999999999999, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "Klass.name, and type_getattro gets called", + "originalText": "Klass.name, and type_getattro gets called", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "d3e8330a405ab419a8e1", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "3b3f0febd4d41e70557c", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 539.5999999999999, + "height": 82.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "231a011ce03d43bc6093", + "type": "text" + }, + { + "id": "a68aeb956196a7265ff8", + "type": "arrow" + }, + { + "id": "f5cd154b4d597bf9b73e", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "231a011ce03d43bc6093", + "type": "text", + "x": 16.0, + "y": 196.0, + "width": 507.5999999999999, + "height": 50.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "walk type(Klass).__mro__, the metaclass\nchain", + "originalText": "walk type(Klass).__mro__, the metaclass\nchain", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3b3f0febd4d41e70557c", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "51ff221e5b8de2681470", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 539.5999999999999, + "height": 70.0, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "712e0b1356f71218daa6", + "type": "text" + }, + { + "id": "f5cd154b4d597bf9b73e", + "type": "arrow" + }, + { + "id": "87c8dd63688f925a07c3", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "712e0b1356f71218daa6", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 507.5999999999999, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "found a data descriptor there, call it", + "originalText": "found a data descriptor there, call it", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "51ff221e5b8de2681470", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "58f65cc3dc6b02b4a0fd", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 539.5999999999999, + "height": 70.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "b3d1bd0de9147417ab10", + "type": "text" + }, + { + "id": "87c8dd63688f925a07c3", + "type": "arrow" + }, + { + "id": "5b3fb1fc65a3152200e0", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "b3d1bd0de9147417ab10", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 507.5999999999999, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "otherwise walk Klass.__mro__ itself", + "originalText": "otherwise walk Klass.__mro__ itself", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "58f65cc3dc6b02b4a0fd", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "cb74e448241b0a756ae6", + "type": "rectangle", + "x": 0.0, + "y": 570.0, + "width": 539.5999999999999, + "height": 70.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "f3ed0bf24d24da4a58d6", + "type": "text" + }, + { + "id": "5b3fb1fc65a3152200e0", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "f3ed0bf24d24da4a58d6", + "type": "text", + "x": 16.0, + "y": 592.5, + "width": 507.5999999999999, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "and call __get__ with None as the instance", + "originalText": "and call __get__ with None as the instance", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "cb74e448241b0a756ae6", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "a68aeb956196a7265ff8", + "type": "arrow", + "x": 269.79999999999995, + "y": 120.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "d3e8330a405ab419a8e1", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "3b3f0febd4d41e70557c", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "e57174e5811d5ca4b94c", + "type": "text", + "x": 281.79999999999995, + "y": 140.0, + "width": 355.91999999999996, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the metaclass comes first, not the class", + "originalText": "the metaclass comes first, not the class", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f5cd154b4d597bf9b73e", + "type": "arrow", + "x": 269.79999999999995, + "y": 262.0, + "width": 0.0, + "height": 48.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 48.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "3b3f0febd4d41e70557c", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "51ff221e5b8de2681470", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "6adbfdcaf3e79146b865", + "type": "text", + "x": 281.79999999999995, + "y": 276.0, + "width": 349.68, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "this is how a metaclass property works", + "originalText": "this is how a metaclass property works", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "87c8dd63688f925a07c3", + "type": "arrow", + "x": 269.79999999999995, + "y": 380.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "51ff221e5b8de2681470", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "58f65cc3dc6b02b4a0fd", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "e14a854458d7d1a7c1cf", + "type": "text", + "x": 281.79999999999995, + "y": 400.0, + "width": 321.84, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the same MRO walk, one level down", + "originalText": "the same MRO walk, one level down", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "5b3fb1fc65a3152200e0", + "type": "arrow", + "x": 269.79999999999995, + "y": 510.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "58f65cc3dc6b02b4a0fd", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "cb74e448241b0a756ae6", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o05-what-a-dot-does/diagrams/a-class-is-different.svg b/lessons/o05-what-a-dot-does/diagrams/a-class-is-different.svg new file mode 100644 index 0000000..9db8adb --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/a-class-is-different.svg @@ -0,0 +1 @@ +Looking a name up on a class takes a different functionKlass.name, and type_getattro gets calledwalk type(Klass).__mro__, the metaclasschainfound a data descriptor there, call itotherwise walk Klass.__mro__ itselfand call __get__ with None as the instancethe metaclass comes first, not the classthis is how a metaclass property worksthe same MRO walk, one level down diff --git a/lessons/o05-what-a-dot-does/diagrams/five-steps-in-order.excalidraw b/lessons/o05-what-a-dot-does/diagrams/five-steps-in-order.excalidraw new file mode 100644 index 0000000..72bc5be --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/five-steps-in-order.excalidraw @@ -0,0 +1,912 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "a0cbd3fb9b5cc2dc381f", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 746.64, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "What PyObject_GenericGetAttr does, in the order it does it", + "originalText": "What PyObject_GenericGetAttr does, in the order it does it", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f17fcd9205a95018379e", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 470.0, + "height": 70.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "c94dbec4de062f462019", + "type": "text" + }, + { + "id": "7d947c85d370bc229f18", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "c94dbec4de062f462019", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 438.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "x.name, and tp_getattro gets called", + "originalText": "x.name, and tp_getattro gets called", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "f17fcd9205a95018379e", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "7978ce4fa2a6b3135a46", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 470.0, + "height": 70.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "94d35cfe6a4ac003e6e6", + "type": "text" + }, + { + "id": "7d947c85d370bc229f18", + "type": "arrow" + }, + { + "id": "d8737d913ef27ef3eeba", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "94d35cfe6a4ac003e6e6", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 438.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "walk type(x).__mro__ for name", + "originalText": "walk type(x).__mro__ for name", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "7978ce4fa2a6b3135a46", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c62822587320e1a9f162", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 470.0, + "height": 70.0, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "603a362d1572534cf7e5", + "type": "text" + }, + { + "id": "d8737d913ef27ef3eeba", + "type": "arrow" + }, + { + "id": "e3e9ef05d99ae79b9fe9", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "603a362d1572534cf7e5", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 438.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "found a data descriptor, call its __get__", + "originalText": "found a data descriptor, call its __get__", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c62822587320e1a9f162", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "2f006ee47a9abbbfd208", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 470.0, + "height": 70.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "c57be2cf6193f31179a3", + "type": "text" + }, + { + "id": "e3e9ef05d99ae79b9fe9", + "type": "arrow" + }, + { + "id": "19a3e40818eaa8906f8c", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "c57be2cf6193f31179a3", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 438.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "otherwise look in the instance dict", + "originalText": "otherwise look in the instance dict", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "2f006ee47a9abbbfd208", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "9871832e45875073383b", + "type": "rectangle", + "x": 0.0, + "y": 570.0, + "width": 470.0, + "height": 70.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "94ef4fd859b08c67402a", + "type": "text" + }, + { + "id": "19a3e40818eaa8906f8c", + "type": "arrow" + }, + { + "id": "194b3f93a09066cc1234", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "94ef4fd859b08c67402a", + "type": "text", + "x": 16.0, + "y": 592.5, + "width": 438.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "otherwise use what the MRO found", + "originalText": "otherwise use what the MRO found", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "9871832e45875073383b", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "47e5434462369b583f40", + "type": "rectangle", + "x": 0.0, + "y": 700.0, + "width": 470.0, + "height": 70.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "9bd383d43d64ff83d9bf", + "type": "text" + }, + { + "id": "194b3f93a09066cc1234", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "9bd383d43d64ff83d9bf", + "type": "text", + "x": 16.0, + "y": 722.5, + "width": 438.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "otherwise raise AttributeError", + "originalText": "otherwise raise AttributeError", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "47e5434462369b583f40", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "7d947c85d370bc229f18", + "type": "arrow", + "x": 235.0, + "y": 120.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "f17fcd9205a95018379e", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "7978ce4fa2a6b3135a46", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "1bff78790e2a576dd0b2", + "type": "text", + "x": 247.0, + "y": 140.0, + "width": 324.0, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "one lookup, cached, described in O04", + "originalText": "one lookup, cached, described in O04", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d8737d913ef27ef3eeba", + "type": "arrow", + "x": 235.0, + "y": 250.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "7978ce4fa2a6b3135a46", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "c62822587320e1a9f162", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "c647bd682d2fa046a042", + "type": "text", + "x": 247.0, + "y": 270.0, + "width": 358.56, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "one that has __set__ as well as __get__", + "originalText": "one that has __set__ as well as __get__", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e3e9ef05d99ae79b9fe9", + "type": "arrow", + "x": 235.0, + "y": 380.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "c62822587320e1a9f162", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "2f006ee47a9abbbfd208", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "bd2464ffd276c1a3d41b", + "type": "text", + "x": 247.0, + "y": 400.0, + "width": 398.88, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "which is why a property cannot be shadowed", + "originalText": "which is why a property cannot be shadowed", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "19a3e40818eaa8906f8c", + "type": "arrow", + "x": 235.0, + "y": 510.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "2f006ee47a9abbbfd208", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "9871832e45875073383b", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "d646522be4ec8eb6d5cc", + "type": "text", + "x": 247.0, + "y": 530.0, + "width": 386.16, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a plain value, or a __get__ with no __set__", + "originalText": "a plain value, or a __get__ with no __set__", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "194b3f93a09066cc1234", + "type": "arrow", + "x": 235.0, + "y": 640.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "9871832e45875073383b", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "47e5434462369b583f40", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o05-what-a-dot-does/diagrams/five-steps-in-order.svg b/lessons/o05-what-a-dot-does/diagrams/five-steps-in-order.svg new file mode 100644 index 0000000..4dca850 --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/five-steps-in-order.svg @@ -0,0 +1 @@ +What PyObject_GenericGetAttr does, in the order it does itx.name, and tp_getattro gets calledwalk type(x).__mro__ for namefound a data descriptor, call its __get__otherwise look in the instance dictotherwise use what the MRO foundotherwise raise AttributeErrorone lookup, cached, described in O04one that has __set__ as well as __get__which is why a property cannot be shadoweda plain value, or a __get__ with no __set__ diff --git a/lessons/o05-what-a-dot-does/diagrams/specialised-then-not.excalidraw b/lessons/o05-what-a-dot-does/diagrams/specialised-then-not.excalidraw new file mode 100644 index 0000000..3f4dffe --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/specialised-then-not.excalidraw @@ -0,0 +1,746 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "fe6c1d53cec7e8c16bc6", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 748.08, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "The bytecode rewrites itself around what it keeps seeing", + "originalText": "The bytecode rewrites itself around what it keeps seeing", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d3b407e429e4c909e659", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 511.7, + "height": 70.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "3b6356f79192e0543543", + "type": "text" + }, + { + "id": "9ed7ab266af547b1baef", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "3b6356f79192e0543543", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 479.7, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "LOAD_ATTR, the general form", + "originalText": "LOAD_ATTR, the general form", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "d3b407e429e4c909e659", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "fe12bed08c5a0574575a", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 511.7, + "height": 70.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "91997a8d053ca56f180e", + "type": "text" + }, + { + "id": "9ed7ab266af547b1baef", + "type": "arrow" + }, + { + "id": "8221f3e29c6c0e4d58ed", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "91997a8d053ca56f180e", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 479.7, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "after a few runs, LOAD_ATTR_INSTANCE_VALUE", + "originalText": "after a few runs, LOAD_ATTR_INSTANCE_VALUE", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "fe12bed08c5a0574575a", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "17f73cac2336e8e70203", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 511.7, + "height": 70.0, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "305541d2eee73244e52f", + "type": "text" + }, + { + "id": "8221f3e29c6c0e4d58ed", + "type": "arrow" + }, + { + "id": "0639e66ee167f1df1034", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "305541d2eee73244e52f", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 479.7, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "someone assigns a property to the class", + "originalText": "someone assigns a property to the class", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "17f73cac2336e8e70203", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c9c7a84338f1fb77acfd", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 511.7, + "height": 70.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "a87456dd86f0b5d28411", + "type": "text" + }, + { + "id": "0639e66ee167f1df1034", + "type": "arrow" + }, + { + "id": "242a774cd6bb19081238", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "a87456dd86f0b5d28411", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 479.7, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "the guard fails and it goes back to general", + "originalText": "the guard fails and it goes back to general", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c9c7a84338f1fb77acfd", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "17b7dda99aba234699a4", + "type": "rectangle", + "x": 0.0, + "y": 570.0, + "width": 511.7, + "height": 70.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "85e59efa72764d62ebef", + "type": "text" + }, + { + "id": "242a774cd6bb19081238", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "85e59efa72764d62ebef", + "type": "text", + "x": 16.0, + "y": 592.5, + "width": 479.7, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "and settles on LOAD_ATTR_PROPERTY", + "originalText": "and settles on LOAD_ATTR_PROPERTY", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "17b7dda99aba234699a4", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "9ed7ab266af547b1baef", + "type": "arrow", + "x": 255.85, + "y": 120.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "d3b407e429e4c909e659", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "fe12bed08c5a0574575a", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "753414d15bd7ecc05041", + "type": "text", + "x": 267.85, + "y": 140.0, + "width": 288.0, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the guard is the type version tag", + "originalText": "the guard is the type version tag", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "8221f3e29c6c0e4d58ed", + "type": "arrow", + "x": 255.85, + "y": 250.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "fe12bed08c5a0574575a", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "17f73cac2336e8e70203", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "d436f9a1d15515629cc9", + "type": "text", + "x": 267.85, + "y": 270.0, + "width": 347.76, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "which zeroes every version tag under it", + "originalText": "which zeroes every version tag under it", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "0639e66ee167f1df1034", + "type": "arrow", + "x": 255.85, + "y": 380.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "17f73cac2336e8e70203", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "c9c7a84338f1fb77acfd", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "b91ac373f73eaa3bc35b", + "type": "text", + "x": 267.85, + "y": 400.0, + "width": 477.36, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "no invalidation pass, just a check that stops matching", + "originalText": "no invalidation pass, just a check that stops matching", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "242a774cd6bb19081238", + "type": "arrow", + "x": 255.85, + "y": 510.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "c9c7a84338f1fb77acfd", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "17b7dda99aba234699a4", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o05-what-a-dot-does/diagrams/specialised-then-not.svg b/lessons/o05-what-a-dot-does/diagrams/specialised-then-not.svg new file mode 100644 index 0000000..d5ef7b4 --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/specialised-then-not.svg @@ -0,0 +1 @@ +The bytecode rewrites itself around what it keeps seeingLOAD_ATTR, the general formafter a few runs, LOAD_ATTR_INSTANCE_VALUEsomeone assigns a property to the classthe guard fails and it goes back to generaland settles on LOAD_ATTR_PROPERTYthe guard is the type version tagwhich zeroes every version tag under itno invalidation pass, just a check that stops matching diff --git a/lessons/o05-what-a-dot-does/diagrams/the-version-tag.excalidraw b/lessons/o05-what-a-dot-does/diagrams/the-version-tag.excalidraw new file mode 100644 index 0000000..ffaea80 --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/the-version-tag.excalidraw @@ -0,0 +1,473 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "e15357c80db1768d3182", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 793.8, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "How the answer gets remembered, and how it gets forgotten", + "originalText": "How the answer gets remembered, and how it gets forgotten", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "3e661aabbf3f249834a4", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 644.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "b8aa20ef1e6e9d331eb4", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "b8aa20ef1e6e9d331eb4", + "type": "text", + "x": 16.0, + "y": 66.0, + "width": 612.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "and zeroes it on every subclass too", + "originalText": "and zeroes it on every subclass too", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "3e661aabbf3f249834a4", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "2fa201ea4bce18ab2d3e", + "type": "text", + "x": 660.0, + "y": 66.0, + "width": 25.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "top", + "originalText": "top", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "8f53ef932dc10fea0532", + "type": "rectangle", + "x": 0.0, + "y": 102.0, + "width": 644.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "5dbbbea1b2f4cbe3757f", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "5dbbbea1b2f4cbe3757f", + "type": "text", + "x": 16.0, + "y": 118.0, + "width": 612.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "changing anything on the class zeroes the tag", + "originalText": "changing anything on the class zeroes the tag", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "8f53ef932dc10fea0532", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "f5d1f42c40e4e523b0ef", + "type": "rectangle", + "x": 0.0, + "y": 154.0, + "width": 644.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "9636bc10cb65aa15e284", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "9636bc10cb65aa15e284", + "type": "text", + "x": 16.0, + "y": 170.0, + "width": 612.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "a hit needs both the version and the name to match", + "originalText": "a hit needs both the version and the name to match", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "f5d1f42c40e4e523b0ef", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "a5ea93e627e5fa89613a", + "type": "rectangle", + "x": 0.0, + "y": 206.0, + "width": 644.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "24370de47fc6cdd1a780", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "24370de47fc6cdd1a780", + "type": "text", + "x": 16.0, + "y": 222.0, + "width": 612.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "the cache stores type version, name, and the answer", + "originalText": "the cache stores type version, name, and the answer", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "a5ea93e627e5fa89613a", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "f84d59b407526af0d608", + "type": "rectangle", + "x": 0.0, + "y": 258.0, + "width": 644.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "03913df591aa159bc9a3", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "03913df591aa159bc9a3", + "type": "text", + "x": 16.0, + "y": 274.0, + "width": 612.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "every type has a tp_version_tag, a plain number", + "originalText": "every type has a tp_version_tag, a plain number", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "f84d59b407526af0d608", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "389e8278a0d11762aefe", + "type": "text", + "x": 0.0, + "y": 330.0, + "width": 720.72, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "There is no invalidation pass. The old entries stay and simply never match again.", + "originalText": "There is no invalidation pass. The old entries stay and simply never match again.", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o05-what-a-dot-does/diagrams/the-version-tag.svg b/lessons/o05-what-a-dot-does/diagrams/the-version-tag.svg new file mode 100644 index 0000000..1f4f602 --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/the-version-tag.svg @@ -0,0 +1 @@ +How the answer gets remembered, and how it gets forgottenand zeroes it on every subclass tootopchanging anything on the class zeroes the taga hit needs both the version and the name to matchthe cache stores type version, name, and the answerevery type has a tp_version_tag, a plain numberThere is no invalidation pass. The old entries stay and simply never match again. diff --git a/lessons/o05-what-a-dot-does/diagrams/two-hooks-not-one.excalidraw b/lessons/o05-what-a-dot-does/diagrams/two-hooks-not-one.excalidraw new file mode 100644 index 0000000..9db1334 --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/two-hooks-not-one.excalidraw @@ -0,0 +1,753 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "f6ecc28e5c4d59577d8e", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 798.1200000000001, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "Two names one letter apart that do completely different jobs", + "originalText": "Two names one letter apart that do completely different jobs", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a86be1064fe25bb585bd", + "type": "text", + "x": 161.5, + "y": 50.0, + "width": 165.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "__getattribute__", + "originalText": "__getattribute__", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "29c3008242ef9b762139", + "type": "rectangle", + "x": 0.0, + "y": 87.0, + "width": 488.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "71664cd68db21aa84b62", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "71664cd68db21aa84b62", + "type": "text", + "x": 16.0, + "y": 103.0, + "width": 456.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "runs for every single attribute", + "originalText": "runs for every single attribute", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "29c3008242ef9b762139", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "5f43cbcae5efa91841e5", + "type": "rectangle", + "x": 0.0, + "y": 147.0, + "width": 488.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "886d230748557111b7db", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "886d230748557111b7db", + "type": "text", + "x": 16.0, + "y": 163.0, + "width": 456.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "including __dict__ and __class__", + "originalText": "including __dict__ and __class__", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "5f43cbcae5efa91841e5", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "6ed0f978598c9857c115", + "type": "rectangle", + "x": 0.0, + "y": 207.0, + "width": 488.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "9584879e6edb3abfefad", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "9584879e6edb3abfefad", + "type": "text", + "x": 16.0, + "y": 223.0, + "width": 456.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "the default one is the C function", + "originalText": "the default one is the C function", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "6ed0f978598c9857c115", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "7bcf5a625543fb430445", + "type": "rectangle", + "x": 0.0, + "y": 267.0, + "width": 488.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "eaaaeb1a5e00128da6bc", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "eaaaeb1a5e00128da6bc", + "type": "text", + "x": 16.0, + "y": 283.0, + "width": 456.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "override it and you own everything", + "originalText": "override it and you own everything", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "7bcf5a625543fb430445", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "20392c41112cb76a5087", + "type": "text", + "x": 735.0, + "y": 50.0, + "width": 114.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "__getattr__", + "originalText": "__getattr__", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "fe58bda1e82d94246f0a", + "type": "rectangle", + "x": 548.0, + "y": 87.0, + "width": 488.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "1259ed9dcf86bbda9f6b", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "1259ed9dcf86bbda9f6b", + "type": "text", + "x": 564.0, + "y": 103.0, + "width": 456.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "runs only after the normal path failed", + "originalText": "runs only after the normal path failed", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "fe58bda1e82d94246f0a", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "481f89b73f7820bc716f", + "type": "rectangle", + "x": 548.0, + "y": 147.0, + "width": 488.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "0153fb7fcd62270cbfb7", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "0153fb7fcd62270cbfb7", + "type": "text", + "x": 564.0, + "y": 163.0, + "width": 456.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "and only on AttributeError", + "originalText": "and only on AttributeError", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "481f89b73f7820bc716f", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "54acca1dee588c532de0", + "type": "rectangle", + "x": 548.0, + "y": 207.0, + "width": 488.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "c32d59240b5e287eeec6", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "c32d59240b5e287eeec6", + "type": "text", + "x": 564.0, + "y": 223.0, + "width": 456.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "there is no default one at all", + "originalText": "there is no default one at all", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "54acca1dee588c532de0", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "d9a1a215230072b7ae57", + "type": "rectangle", + "x": 548.0, + "y": 267.0, + "width": 488.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "86c5b6e68f079e5b20cc", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "86c5b6e68f079e5b20cc", + "type": "text", + "x": 564.0, + "y": 283.0, + "width": 456.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "override it and you own the gaps", + "originalText": "override it and you own the gaps", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "d9a1a215230072b7ae57", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "9888fe8621188cd6b7bf", + "type": "rectangle", + "x": 0.0, + "y": 344.0, + "width": 1036.0, + "height": 82.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "9ae9b6f05c8d188d7be5", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "9ae9b6f05c8d188d7be5", + "type": "text", + "x": 16.0, + "y": 360.0, + "width": 1004.0, + "height": 50.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "The slot is the same either way. A class with __getattr__ gets the hook dispatcher\ninstead.", + "originalText": "The slot is the same either way. A class with __getattr__ gets the hook dispatcher\ninstead.", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "9888fe8621188cd6b7bf", + "lineHeight": 1.25, + "autoResize": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o05-what-a-dot-does/diagrams/two-hooks-not-one.svg b/lessons/o05-what-a-dot-does/diagrams/two-hooks-not-one.svg new file mode 100644 index 0000000..4ff4f7f --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/two-hooks-not-one.svg @@ -0,0 +1 @@ +Two names one letter apart that do completely different jobs__getattribute__runs for every single attributeincluding __dict__ and __class__the default one is the C functionoverride it and you own everything__getattr__runs only after the normal path failedand only on AttributeErrorthere is no default one at alloverride it and you own the gapsThe slot is the same either way. A class with __getattr__ gets the hook dispatcherinstead. diff --git a/lessons/o05-what-a-dot-does/diagrams/who-wins.excalidraw b/lessons/o05-what-a-dot-does/diagrams/who-wins.excalidraw new file mode 100644 index 0000000..84ef349 --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/who-wins.excalidraw @@ -0,0 +1,1235 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "f5c07f33e9139ac258cd", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 667.4399999999999, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "The only row that surprises people is the first one", + "originalText": "The only row that surprises people is the first one", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e7db54b36af90b50ec03", + "type": "text", + "x": 14.0, + "y": 50.0, + "width": 173.76, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "what is on the type", + "originalText": "what is on the type", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f95d00d9bd8b6bc5e56c", + "type": "text", + "x": 243.6, + "y": 50.0, + "width": 245.76, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "what is in the instance dict", + "originalText": "what is in the instance dict", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d63003303521ca4c25ca", + "type": "text", + "x": 540.4, + "y": 50.0, + "width": 202.32, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "what x.name gives you", + "originalText": "what x.name gives you", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d6936afa83bd4d41fed0", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 861.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "06ece8ad7c106c9ca281", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 861.6, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ], + [ + 861.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "6ff8fad83f70af13ef18", + "type": "text", + "x": 14.0, + "y": 86.0, + "width": 163.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a data descriptor", + "originalText": "a data descriptor", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "58b2bd12cc2412a843f5", + "type": "text", + "x": 243.6, + "y": 86.0, + "width": 67.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a value", + "originalText": "a value", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "5dd5304f961732759fe2", + "type": "text", + "x": 540.4, + "y": 86.0, + "width": 134.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the descriptor", + "originalText": "the descriptor", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "05e690e144c3dd038847", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 861.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "d9d38a7bad6cf72ab934", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 861.6, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ], + [ + 861.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "38c249de5045ee598ff8", + "type": "text", + "x": 14.0, + "y": 126.0, + "width": 201.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a non data descriptor", + "originalText": "a non data descriptor", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f2d724fbb6c16fa588fa", + "type": "text", + "x": 243.6, + "y": 126.0, + "width": 67.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a value", + "originalText": "a value", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "b9045b08b543ed2f672f", + "type": "text", + "x": 540.4, + "y": 126.0, + "width": 163.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the instance dict", + "originalText": "the instance dict", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "6bbfcc6b049c2f911f98", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 861.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "a1377717e76570352e4d", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 861.6, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ], + [ + 861.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "24939cbecf9861e5273e", + "type": "text", + "x": 14.0, + "y": 166.0, + "width": 124.8, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a plain value", + "originalText": "a plain value", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "0668ae21b22fd163b448", + "type": "text", + "x": 243.6, + "y": 166.0, + "width": 67.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a value", + "originalText": "a value", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a5e73ddf8dea24e484c5", + "type": "text", + "x": 540.4, + "y": 166.0, + "width": 163.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the instance dict", + "originalText": "the instance dict", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "582e91880328c286cc10", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 861.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "f7edf1ad322f6b426505", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 861.6, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ], + [ + 861.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "62036c3378d29260df51", + "type": "text", + "x": 14.0, + "y": 206.0, + "width": 201.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a non data descriptor", + "originalText": "a non data descriptor", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "3481317e61dd4d6e1fc8", + "type": "text", + "x": 243.6, + "y": 206.0, + "width": 67.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "nothing", + "originalText": "nothing", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "51034b0a08db74307384", + "type": "text", + "x": 540.4, + "y": 206.0, + "width": 134.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the descriptor", + "originalText": "the descriptor", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "de0511648809cb9f7115", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 861.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "44c24fbcad12980b2e34", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 861.6, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ], + [ + 861.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "373e16fcda33eba7b7bd", + "type": "text", + "x": 14.0, + "y": 246.0, + "width": 67.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "nothing", + "originalText": "nothing", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d6cc5585c2ffb3b76405", + "type": "text", + "x": 243.6, + "y": 246.0, + "width": 67.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "nothing", + "originalText": "nothing", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d6be61eae81e50f79c30", + "type": "text", + "x": 540.4, + "y": 246.0, + "width": 307.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "AttributeError, then __getattr__", + "originalText": "AttributeError, then __getattr__", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "960bf9081be0e91e201b", + "type": "line", + "x": 0.0, + "y": 276.0, + "width": 861.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 861.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "fbaebe7a264d758b24d0", + "type": "text", + "x": 0.0, + "y": 296.0, + "width": 817.44, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "A data descriptor is one with __set__ or __delete__, which is what property and slots make.", + "originalText": "A data descriptor is one with __set__ or __delete__, which is what property and slots make.", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o05-what-a-dot-does/diagrams/who-wins.svg b/lessons/o05-what-a-dot-does/diagrams/who-wins.svg new file mode 100644 index 0000000..e35b386 --- /dev/null +++ b/lessons/o05-what-a-dot-does/diagrams/who-wins.svg @@ -0,0 +1 @@ +The only row that surprises people is the first onewhat is on the typewhat is in the instance dictwhat x.name gives youa data descriptora valuethe descriptora non data descriptora valuethe instance dicta plain valuea valuethe instance dicta non data descriptornothingthe descriptornothingnothingAttributeError, then __getattr__A data descriptor is one with __set__ or __delete__, which is what property and slots make. diff --git a/lessons/o05-what-a-dot-does/o05.ipynb b/lessons/o05-what-a-dot-does/o05.ipynb new file mode 100644 index 0000000..bb5be0b --- /dev/null +++ b/lessons/o05-what-a-dot-does/o05.ipynb @@ -0,0 +1,559 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "o05-01", + "metadata": {}, + "source": [ + "# O05. What a dot does\n", + "\n", + "[![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)\n", + "\n", + "O04 finished with a four line loop: walk the MRO, do a dict lookup in each class, stop at the first hit. That loop is real, but it is only one of four things that happen when you write `x.name`, and it is not the first.\n", + "\n", + "![the four places a lookup checks, in the fixed order it checks them](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o05-what-a-dot-does/diagrams/five-steps-in-order.svg)\n", + "\n", + "Everything people learn about attributes as a separate rule is this one order. A property cannot be shadowed by an instance attribute, and a method can. `__getattr__` runs sometimes and `__getattribute__` runs always. Looking a name up on a class behaves differently from looking it up on an instance. Four rules, one function." + ] + }, + { + "cell_type": "markdown", + "id": "o05-02", + "metadata": {}, + "source": [ + "## About the source references\n", + "\n", + "Now and then this lesson points at CPython's own source, like this: `Objects/object.c:1887-1926@v3.15.0rc1#_PyObject_GenericGetAttrWithDict`.\n", + "\n", + "Read it as three parts: the file, the lines, and the release those line numbers belong to. Sometimes there is a fourth part after a `#`, which is the name of the thing those lines are inside.\n", + "\n", + "Every reference is a link, and every one is checked against the pinned source on each change, so a stale reference fails the build instead of sending you somewhere wrong. You never have to read any of it. The references are there so you can go deeper when you want to, and so you can check that this lesson is not making things up.\n", + "\n", + "## Setup\n", + "\n", + "Colab does not come with the small package these lessons use, so the next cell installs it. If you are running this from a checkout of the repository it is already installed and the cell does nothing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o05-03", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "\n", + "if sys.version_info < (3, 14):\n", + " print(\"This lesson needs CPython 3.14 or newer.\")\n", + " print(f\"This runtime is {sys.version.split()[0]}, and the cells below will not run on it.\")\n", + "else:\n", + " try:\n", + " import pyxray\n", + " except ImportError:\n", + " %pip install -q \"pyxray @ git+https://github.com/tamnd/cpython-internals@main#subdirectory=pyxray\"\n", + " import pyxray" + ] + }, + { + "cell_type": "markdown", + "id": "o05-04", + "metadata": {}, + "source": [ + "## Which Python is this\n", + "\n", + "Everything below was checked against the version this cell prints and against 3.14. One cell has a different wording on the two, and it says so." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o05-05", + "metadata": { + "cpython_internals": { + "differs": "This prints the interpreter you are on, so it is different for everybody." + } + }, + "outputs": [], + "source": [ + "import pyxray\n", + "\n", + "pyxray.show()" + ] + }, + { + "cell_type": "markdown", + "id": "o05-06", + "metadata": {}, + "source": [ + "## Four places, one order\n", + "\n", + "The function is `_PyObject_GenericGetAttrWithDict`, [Objects/object.c:1887-1926@v3.15.0rc1#_PyObject_GenericGetAttrWithDict](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/object.c#L1887-L1926), and `PyObject_GenericGetAttr` is a one line wrapper around it. Read past the error checking and it does four things in this order.\n", + "\n", + "One: walk the type's MRO for the name. That is O04's loop, and the result is called `descr` for the rest of the function.\n", + "\n", + "Two: if what came back has a `__get__` and also a `__set__`, call `__get__` right there and return. Something with both is a [data descriptor](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#data-descriptor), and this branch is the only reason a property wins.\n", + "\n", + "Three: otherwise look in the instance dict, and return that if it is there, [Objects/object.c:1964-1979@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/object.c#L1964-L1979).\n", + "\n", + "Four: otherwise fall back on what the MRO found. If it has a `__get__`, call it. If it is a plain value, return it. If there was nothing at all, raise `AttributeError`, [Objects/object.c:1981-2002@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/object.c#L1981-L2002).\n", + "\n", + "![which of the type and the instance dict wins, for each kind of thing on the type](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o05-what-a-dot-does/diagrams/who-wins.svg)\n", + "\n", + "So the type is consulted first but usually does not win, because step two only fires for a data descriptor. The whole of the famous precedence rule is the position of one `if`.\n", + "\n", + "with the same name present on the type and in the instance dict, a data descriptor on the type wins and a non data descriptor loses, and the instance dict entry is untouched either way" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o05-07", + "metadata": {}, + "outputs": [], + "source": [ + "class NonData:\n", + " def __get__(self, obj, owner):\n", + " return \"the non data descriptor on the type\"\n", + "\n", + "\n", + "class Data:\n", + " def __get__(self, obj, owner):\n", + " return \"the data descriptor on the type\"\n", + "\n", + " def __set__(self, obj, value):\n", + " pass\n", + "\n", + "\n", + "class Sample:\n", + " plain = \"a plain class attribute\"\n", + " nondata = NonData()\n", + " data = Data()\n", + "\n", + "\n", + "s = Sample()\n", + "for name in [\"data\", \"plain\", \"nondata\"]:\n", + " s.__dict__[name] = \"the instance dict\"\n", + "\n", + "for name in [\"data\", \"plain\", \"nondata\"]:\n", + " print(f\" s.{name:8} {getattr(s, name)}\")\n", + " print(f\" and s.__dict__[{name!r}] is still {s.__dict__[name]!r}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o05-08", + "metadata": {}, + "source": [ + "The instance dict entry for `data` is right there and the lookup ignored it. That is not a special case for properties, it is step two firing before step three.\n", + "\n", + "This also explains something that sounds unrelated. A method is a function, and a function has `__get__` but no `__set__`, so it is a non data descriptor and step three beats it. That is why `obj.method = something` works and shadows the method for that one object, while `obj.some_property = something` does not shadow anything and instead calls the property's setter.\n", + "\n", + "## Two hooks that are not the same hook\n", + "\n", + "`__getattribute__` and `__getattr__` differ by five characters and by almost everything else.\n", + "\n", + "![when each of the two hooks runs and what happens when it does not](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o05-what-a-dot-does/diagrams/two-hooks-not-one.svg)\n", + "\n", + "`__getattribute__` is the slot. Everything above is the default implementation of it. Override it and you replace the whole four step function, for every attribute, including `__dict__` and `__class__`.\n", + "\n", + "`__getattr__` is not a slot at all. There is no default. It is a name the slot dispatcher looks for, and if a class has one, `update_one_slot` installs a different dispatcher called `_Py_slot_tp_getattr_hook`, [Objects/typeobject.c:10990-11026@v3.15.0rc1#_Py_slot_tp_getattr_hook](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L10990-L11026). That one runs the normal lookup with errors suppressed, and only calls your `__getattr__` if the normal lookup came back empty.\n", + "\n", + "There is a small piece of housekeeping worth seeing in that function. If a class has no `__getattr__`, the hook dispatcher swaps itself out of the slot for the simpler one and never runs again, [Objects/typeobject.c:11002-11009@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L11002-L11009).\n", + "\n", + "__getattr__ runs only for names the normal lookup failed to find, while an overridden __getattribute__ runs for every name including __dict__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o05-09", + "metadata": {}, + "outputs": [], + "source": [ + "class Fallback:\n", + " on_the_class = 1\n", + "\n", + " def __getattr__(self, name):\n", + " return f\"__getattr__ made up {name!r}\"\n", + "\n", + "\n", + "f = Fallback()\n", + "f.on_the_instance = 2\n", + "\n", + "print(f\" f.on_the_class {f.on_the_class}\")\n", + "print(f\" f.on_the_instance {f.on_the_instance}\")\n", + "print(f\" f.missing {f.missing}\")\n", + "\n", + "\n", + "class Every:\n", + " def __getattribute__(self, name):\n", + " return f\"__getattribute__ saw {name!r}\"\n", + "\n", + " def __getattr__(self, name):\n", + " return \"this is never reached\"\n", + "\n", + "\n", + "e = Every()\n", + "e.here = \"this really is in the instance dict\"\n", + "\n", + "print()\n", + "print(f\" e.here {e.here}\")\n", + "print(f\" e.missing {e.missing}\")\n", + "print(f\" e.__dict__ {e.__dict__}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o05-10", + "metadata": {}, + "source": [ + "The last line is the one to sit with. `e.__dict__` went through `__getattribute__` too, so there is now no ordinary way to see what is actually in that object. `object.__getattribute__(e, \"__dict__\")` still works, because that calls the C function directly instead of going through the type.\n", + "\n", + "Both hooks live on the type, like every other dunder in O03, so setting `__getattr__` on an instance does nothing at all.\n", + "\n", + "assigning __getattr__ to an instance leaves it in the instance dict and has no effect on attribute lookup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o05-11", + "metadata": {}, + "outputs": [], + "source": [ + "class Quiet:\n", + " pass\n", + "\n", + "\n", + "q = Quiet()\n", + "q.__getattr__ = lambda name: \"set on the instance\"\n", + "\n", + "print(f\" it is in the instance dict {'__getattr__' in q.__dict__}\")\n", + "try:\n", + " value = q.missing\n", + "except AttributeError as error:\n", + " print(f\" and q.missing still raises AttributeError: {error}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o05-12", + "metadata": {}, + "source": [ + "## A class is looked up differently\n", + "\n", + "`Klass.name` does not run `PyObject_GenericGetAttr`. A type is an object whose type is `type`, so the slot that runs is `type`'s own `tp_getattro`, which is `_Py_type_getattro_stackref`, [Objects/typeobject.c:6570-6600@v3.15.0rc1#_Py_type_getattro_stackref](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L6570-L6600).\n", + "\n", + "![the metaclass MRO first, then the class's own MRO](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o05-what-a-dot-does/diagrams/a-class-is-different.svg)\n", + "\n", + "Same shape, one level up. It looks in the metaclass's MRO first, and if it finds a data descriptor there it calls it and stops, [Objects/typeobject.c:6600-6616@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L6600-L6616). Only then does it look in the class's own MRO, [Objects/typeobject.c:6618-6650@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L6618-L6650).\n", + "\n", + "There is one more difference and it is the one that matters in practice. When this function finds a descriptor on the class, it calls `__get__` with `NULL` for the instance, because there is no instance. That is the `None` you get as the second argument, and it is how a descriptor tells `Klass.thing` from `Klass().thing`.\n", + "\n", + "a name defined on the metaclass is reachable through the class and not through an instance of it, and a descriptor found on a class is called with None where the instance would be" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o05-13", + "metadata": {}, + "outputs": [], + "source": [ + "class Meta(type):\n", + " from_the_metaclass = \"found on the metaclass\"\n", + "\n", + "\n", + "class Klass(metaclass=Meta):\n", + " from_the_class = \"found on the class\"\n", + "\n", + "\n", + "print(f\" Klass.from_the_class {Klass.from_the_class}\")\n", + "print(f\" Klass.from_the_metaclass {Klass.from_the_metaclass}\")\n", + "try:\n", + " value = Klass().from_the_metaclass\n", + "except AttributeError as error:\n", + " print(f\" Klass().from_the_metaclass AttributeError: {error}\")\n", + "\n", + "\n", + "class Watcher:\n", + " def __get__(self, obj, owner):\n", + " return f\"obj={obj!r} owner={owner.__name__}\"\n", + "\n", + "\n", + "class Holder:\n", + " watched = Watcher()\n", + "\n", + " def __repr__(self):\n", + " return \"a Holder\"\n", + "\n", + "\n", + "print()\n", + "print(f\" Holder.watched {Holder.watched}\")\n", + "print(f\" Holder().watched {Holder().watched}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o05-14", + "metadata": {}, + "source": [ + "The two error messages are different too, and now you know why: one comes out of the object function and one out of the type function.\n", + "\n", + "the AttributeError for an instance and for a class have different wording, and the exception carries the name and the object as attributes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o05-15", + "metadata": {}, + "outputs": [], + "source": [ + "class Empty:\n", + " pass\n", + "\n", + "\n", + "for target, label in [(Empty(), \"an instance\"), (Empty, \"the class\")]:\n", + " try:\n", + " value = target.nope\n", + " except AttributeError as error:\n", + " print(f\" {label:12} {error}\")\n", + "\n", + "\n", + "class Config:\n", + " def __init__(self):\n", + " self.threshold = 1\n", + "\n", + "\n", + "try:\n", + " value = Config().treshold\n", + "except AttributeError as error:\n", + " print()\n", + " print(f\" error.name {error.name!r}\")\n", + " print(f\" error.obj is it {isinstance(error.obj, Config)}\")\n", + " print(f\" the message {error}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o05-16", + "metadata": {}, + "source": [ + "The `name` and `obj` attributes are not decoration. `_PyObject_SetAttributeErrorContext` attaches them on the way out, [Objects/object.c:1996-2002@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/object.c#L1996-L2002), and the traceback machinery uses them later to look for a near miss among the attributes the object actually has. So the suggestion is not in the message. It is added at print time, out of `obj`, and it costs nothing unless the program is already on its way to a traceback.\n", + "\n", + "the did you mean suggestion is not part of the exception message and only appears when a traceback is formatted, because it is computed from the obj attribute at that point" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o05-17", + "metadata": { + "cpython_internals": { + "differs": "On 3.14 the suggestion is worded `Did you mean: 'threshold'?`. 3.15 spells out both names with the dot in front, which reads better when the name is a long one." + } + }, + "outputs": [], + "source": [ + "import traceback\n", + "\n", + "try:\n", + " value = Config().treshold\n", + "except AttributeError as error:\n", + " print(f\" str(error) {error}\")\n", + " print(f\" the traceback {traceback.format_exc().splitlines()[-1]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o05-18", + "metadata": {}, + "source": [ + "> **Version note.** On 3.14 the suggestion is worded `Did you mean: 'threshold'?`. 3.15 spells out both names with the dot in front, which reads better when the name is a long one." + ] + }, + { + "cell_type": "markdown", + "id": "o05-19", + "metadata": {}, + "source": [ + "## How this is not slow\n", + "\n", + "Steps one and two happen on every single attribute access, and step one is a walk over the MRO doing a dict lookup per class. For `self.x` inside a loop that would be several dict lookups per iteration, which would be terrible.\n", + "\n", + "It is not, because of a cache. Each interpreter has one flat hash table of four thousand and ninety six entries, and each entry holds a type version, a name, and the answer, [Objects/typeobject.c:6306-6345@v3.15.0rc1#_PyType_LookupStackRefAndVersion](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L6306-L6345).\n", + "\n", + "![the version tag, and what happens to it when a class changes](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o05-what-a-dot-does/diagrams/the-version-tag.svg)\n", + "\n", + "The version is `tp_version_tag`, a number on the type. A hit needs both the version and the interned name pointer to match, so one comparison of two words replaces the whole walk.\n", + "\n", + "Invalidation is the elegant part. There is no pass over the cache looking for stale entries. `type_modified_unlocked` sets `tp_version_tag` to zero on the class and on every subclass, [Objects/typeobject.c:1166-1195@v3.15.0rc1#type_modified_unlocked](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L1166-L1195), and every entry that mentioned the old version simply stops matching. A type with a zero tag is never cached at all.\n", + "\n", + "There are two limits worth knowing. A name longer than a hundred characters is never cached, [Objects/typeobject.c:44-58@v3.15.0rc1#MCACHE_CACHEABLE_NAME](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L44-L58), and a class that has been modified a thousand times stops getting new version tags for good, [Objects/typeobject.c:1389-1392@v3.15.0rc1#MAX_VERSIONS_PER_CLASS](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L1389-L1392). The second one is why a class you keep reassigning attributes on gets permanently slower.\n", + "\n", + "the cache and the version tag are not visible from Python at all, since nothing exposes tp_version_tag, the hit rate, or the cache contents\n", + "\n", + "## Where you can see it\n", + "\n", + "The cache is invisible, but the thing built on top of it is not. After a piece of bytecode has done the same kind of lookup a few times, the interpreter rewrites the instruction in place into a form that assumes the shape it keeps seeing. `dis` will show you the rewritten form if you ask for it.\n", + "\n", + "![the instruction changing form, failing its guard, and settling on a new one](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o05-what-a-dot-does/diagrams/specialised-then-not.svg)\n", + "\n", + "The guard on the specialised instruction is that same version tag, which is the connection. This is the very short version of a story that belongs to the interpreter part, but the attribute lookup case is the clearest one and it is right here.\n", + "\n", + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o05-20", + "metadata": {}, + "outputs": [], + "source": [ + "import dis\n", + "import math\n", + "\n", + "\n", + "def attr_forms(func):\n", + " return [i.opname for i in dis.get_instructions(func, adaptive=True) if \"LOAD_ATTR\" in i.opname]\n", + "\n", + "\n", + "def show(func, label):\n", + " print(f\" {label:32} {attr_forms(func)}\")\n", + "\n", + "\n", + "class Point:\n", + " def __init__(self, x):\n", + " self.x = x\n", + "\n", + " def scaled(self):\n", + " return self.x * 2\n", + "\n", + "\n", + "class Slotted:\n", + " __slots__ = (\"x\",)\n", + "\n", + " def __init__(self, x):\n", + " self.x = x\n", + "\n", + "\n", + "def read_value(point):\n", + " return point.x\n", + "\n", + "\n", + "def read_slot(point):\n", + " return point.x\n", + "\n", + "\n", + "def read_method(point):\n", + " return point.scaled()\n", + "\n", + "\n", + "def read_module(module):\n", + " return module.pi\n", + "\n", + "\n", + "point = Point(1)\n", + "show(read_value, \"before it has ever run\")\n", + "read_value(point)\n", + "show(read_value, \"after one call\")\n", + "\n", + "for _ in range(100):\n", + " read_value(point)\n", + " read_slot(Slotted(1))\n", + " read_method(point)\n", + " read_module(math)\n", + "\n", + "show(read_value, \"after a hundred\")\n", + "show(read_slot, \"a __slots__ class instead\")\n", + "show(read_method, \"a method rather than a value\")\n", + "show(read_module, \"an attribute of a module\")" + ] + }, + { + "cell_type": "markdown", + "id": "o05-21", + "metadata": {}, + "source": [ + "Four different instructions, from four identical looking lines of Python. `LOAD_ATTR_INSTANCE_VALUE` reads a fixed offset in the inline values array with no dict lookup at all. `LOAD_ATTR_SLOT` reads a fixed offset in the object. `LOAD_ATTR_MODULE` skips straight to a module's dict. `LOAD_ATTR_METHOD_WITH_VALUES` fetches the function without building a bound method object.\n", + "\n", + "Now break the assumption and watch it recover.\n", + "\n", + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o05-22", + "metadata": {}, + "outputs": [], + "source": [ + "show(read_value, \"specialised on instance values\")\n", + "\n", + "Point.x = property(lambda self: 99)\n", + "show(read_value, \"right after the class changed\")\n", + "\n", + "read_value(point)\n", + "show(read_value, \"after running it once more\")\n", + "\n", + "for _ in range(100):\n", + " read_value(point)\n", + "show(read_value, \"and after a hundred more\")\n", + "\n", + "print(f\" and read_value(point) now gives {read_value(point)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o05-23", + "metadata": {}, + "source": [ + "The instruction did not change the moment the class did. It changed the next time it ran and its guard failed, and even then it went back to the general form first and waited a while before committing to the new shape. That backoff is deliberate: an object that keeps changing shape should not pay to re specialise on every access.\n", + "\n", + "## Try it yourself\n", + "\n", + "Three things to poke at.\n", + "\n", + "Write a class with `__getattribute__` that prints the name and then calls `object.__getattribute__` for the real answer, and use one of its instances normally for a few lines. The list of names that go through it is longer than you expect, and most of them are dunders you never wrote.\n", + "\n", + "Take the precedence table and add a fifth row: a data descriptor on the type whose `__get__` raises `AttributeError`. Then call `hasattr`. The lookup function suppresses that one exception and nothing else, which is why `hasattr` returns `False` there and propagates a `ValueError` from the same place.\n", + "\n", + "Time `point.x` on a plain class against the same attribute on a `__slots__` class, a thousand accesses each, using `timeit`. Then assign something new to the class in between and time it again. The numbers are the only view you get of the cache, and they move.\n", + "\n", + "## What just happened\n", + "\n", + "`x.name` is one function doing four things in a fixed order: walk the type's MRO, use what it found if that is a data descriptor, otherwise use the instance dict, otherwise fall back on what the MRO found and raise if there was nothing.\n", + "\n", + "Everything that gets taught as a precedence rule is the position of one `if` in that function. A property has `__set__`, so it wins. A function does not, so an instance attribute of the same name shadows the method.\n", + "\n", + "`__getattribute__` is the slot and runs for everything. `__getattr__` is not a slot, has no default, and only runs when the normal path came back empty. A class with one gets a different dispatcher installed, and that dispatcher removes itself when there is nothing to dispatch to.\n", + "\n", + "Looking a name up on a class runs a different function that checks the metaclass first and then the class, and calls any descriptor it finds with `None` where the instance would go. The two `AttributeError` messages are different because they come from different functions, and the exception carries the name and the object so a traceback can suggest a near miss.\n", + "\n", + "All of it is fast because of a four thousand entry cache keyed on a version number per type. Changing anything on a class zeroes that number on the class and every subclass, and every cached entry quietly stops matching. Nothing about the cache is visible from Python, but the specialised bytecode built on top of it is, and it takes a different form for instance values, slots, modules and methods.\n", + "\n", + "## What is next\n", + "\n", + "O06 is descriptors properly. This lesson used them as a thing that either has `__set__` or does not. The next one is about what they are: `property`, `classmethod`, `staticmethod` and the bound method you get from `obj.method` are all the same protocol, and three of the four are less than twenty lines of C." + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/probes/pyodide/lessons.json b/probes/pyodide/lessons.json index fe893f8..d8208f2 100644 --- a/probes/pyodide/lessons.json +++ b/probes/pyodide/lessons.json @@ -539,7 +539,7 @@ { "name": "f10-11", "status": "ok", - "printed": " -- MAKE_CELL 0 (a)\n MAKE_CELL 6 (n)\n\n 1 RESUME 0\n\n 2 LOAD_SMALL_INT 0\n STORE_DEREF 6 (n)\n\n 4 LOAD_FAST_BORROW 0 (a)\n LOAD_FAST_BORROW 6 (n)\n BUILD_TUPLE 2\n LOAD_CONST 1 ()\n MAKE_FUNCTION\n SET_FUNCTION_ATTRIBUTE 8 (closure)\n STORE_FAST 5 (inner)\n\n 7 LOAD_FAST_BORROW 5 (inner)\n RETURN_VALUE\n\nDisassembly of :\n -- COPY_FREE_VARS 2\n\n 4 RESUME 0\n\n 5 LOAD_DEREF 0 (a)\n LOAD_DEREF 1 (n)\n BINARY_OP 0 (+)\n RETURN_VALUE\n" + "printed": " -- MAKE_CELL 0 (a)\n MAKE_CELL 6 (n)\n\n 1 RESUME 0\n\n 2 LOAD_SMALL_INT 0\n STORE_DEREF 6 (n)\n\n 4 LOAD_FAST_BORROW 0 (a)\n LOAD_FAST_BORROW 6 (n)\n BUILD_TUPLE 2\n LOAD_CONST 1 ()\n MAKE_FUNCTION\n SET_FUNCTION_ATTRIBUTE 8 (closure)\n STORE_FAST 5 (inner)\n\n 7 LOAD_FAST_BORROW 5 (inner)\n RETURN_VALUE\n\nDisassembly of :\n -- COPY_FREE_VARS 2\n\n 4 RESUME 0\n\n 5 LOAD_DEREF 0 (a)\n LOAD_DEREF 1 (n)\n BINARY_OP 0 (+)\n RETURN_VALUE\n" }, { "name": "f10-13", @@ -631,12 +631,12 @@ { "name": "f12-07", "status": "ok", - "printed": " greet.py is 54 bytes of text\n greet.cpython-314.pyc is 251 bytes\n\n header 2b 0e 0d 0a 00 00 00 00 49 a9 95 6a 36 00 00 00\n the rest 235 bytes, and marshal.loads gives back a\n code object\n" + "printed": " greet.py is 54 bytes of text\n greet.cpython-314.pyc is 251 bytes\n\n header 2b 0e 0d 0a 00 00 00 00 42 ad 95 6a 36 00 00 00\n the rest 235 bytes, and marshal.loads gives back a\n code object\n" }, { "name": "f12-10", "status": "ok", - "printed": " magic 168627755\n flags 0\n mtime 1788193097 source 1788193097\n size 54 source 54\n" + "printed": " magic 168627755\n flags 0\n mtime 1788194114 source 1788194114\n size 54 source 54\n" }, { "name": "f12-13", @@ -670,7 +670,7 @@ { "name": "f12-29", "status": "ok", - "printed": " one bit flipped in the magic: bad magic number in 'broken': b'*\\x0e\\r\\n'\n\n put a wrong mtime in greet.cpython-314.pyc: 1788193206\n imported it with no complaint: 'hi world'\n and the header now says 1788193206\n the file was quietly rewritten: False\n" + "printed": " one bit flipped in the magic: bad magic number in 'broken': b'*\\x0e\\r\\n'\n\n put a wrong mtime in greet.cpython-314.pyc: 1788194237\n imported it with no complaint: 'hi world'\n and the header now says 1788194237\n the file was quietly rewritten: False\n" } ] }, @@ -689,7 +689,7 @@ { "name": "o01-07", "status": "ok", - "printed": " a pointer here is 4 bytes, so the header is 8\n it lives at 23912464\n\n first word, read as a count 1\n sys.getrefcount, less its own argument 1\n the same number: True\n\n second word 21607608\n id(Thing) is 21607608\n so the second word is the type pointer: True\n\n after a second name, the count reads 2\n after deleting that name, it reads 1\n" + "printed": " a pointer here is 4 bytes, so the header is 8\n it lives at 25574120\n\n first word, read as a count 1\n sys.getrefcount, less its own argument 1\n the same number: True\n\n second word 24505536\n id(Thing) is 24505536\n so the second word is the type pointer: True\n\n after a second name, the count reads 2\n after deleting that name, it reads 1\n" }, { "name": "o01-10", @@ -875,6 +875,60 @@ } ] }, + { + "slug": "o05-what-a-dot-does", + "cells": [ + { + "name": "o05-03", + "status": "ok" + }, + { + "name": "o05-05", + "status": "ok", + "printed": "cpython 3.14.2 on Emscripten wasm32, WebAssembly\n note: this is 3.14, and everything here is written against 3.15, so bytecode and some object layouts will differ from the prose\n" + }, + { + "name": "o05-07", + "status": "ok", + "printed": " s.data the data descriptor on the type\n and s.__dict__['data'] is still 'the instance dict'\n s.plain the instance dict\n and s.__dict__['plain'] is still 'the instance dict'\n s.nondata the instance dict\n and s.__dict__['nondata'] is still 'the instance dict'\n" + }, + { + "name": "o05-09", + "status": "ok", + "printed": " f.on_the_class 1\n f.on_the_instance 2\n f.missing __getattr__ made up 'missing'\n\n e.here __getattribute__ saw 'here'\n e.missing __getattribute__ saw 'missing'\n e.__dict__ __getattribute__ saw '__dict__'\n" + }, + { + "name": "o05-11", + "status": "ok", + "printed": " it is in the instance dict True\n and q.missing still raises AttributeError: 'Quiet' object has no attribute 'missing'\n" + }, + { + "name": "o05-13", + "status": "ok", + "printed": " Klass.from_the_class found on the class\n Klass.from_the_metaclass found on the metaclass\n Klass().from_the_metaclass AttributeError: 'Klass' object has no attribute 'from_the_metaclass'\n\n Holder.watched obj=None owner=Holder\n Holder().watched obj=a Holder owner=Holder\n" + }, + { + "name": "o05-15", + "status": "ok", + "printed": " an instance 'Empty' object has no attribute 'nope'\n the class type object 'Empty' has no attribute 'nope'\n\n error.name 'treshold'\n error.obj is it True\n the message 'Config' object has no attribute 'treshold'\n" + }, + { + "name": "o05-17", + "status": "ok", + "printed": " str(error) 'Config' object has no attribute 'treshold'\n the traceback AttributeError: 'Config' object has no attribute 'treshold'. Did you mean: 'threshold'?\n" + }, + { + "name": "o05-20", + "status": "ok", + "printed": " before it has ever run ['LOAD_ATTR']\n after one call ['LOAD_ATTR']\n after a hundred ['LOAD_ATTR_INSTANCE_VALUE']\n a __slots__ class instead ['LOAD_ATTR_SLOT']\n a method rather than a value ['LOAD_ATTR_METHOD_WITH_VALUES']\n an attribute of a module ['LOAD_ATTR_MODULE']\n" + }, + { + "name": "o05-22", + "status": "ok", + "printed": " specialised on instance values ['LOAD_ATTR_INSTANCE_VALUE']\n right after the class changed ['LOAD_ATTR_INSTANCE_VALUE']\n after running it once more ['LOAD_ATTR_INSTANCE_VALUE']\n and after a hundred more ['LOAD_ATTR_PROPERTY']\n and read_value(point) now gives 99\n" + } + ] + }, { "slug": "t01-one-line-seven-stages", "cells": [ @@ -1257,7 +1311,7 @@ { "name": "t04-30", "status": "ok", - "printed": "free variables: ('total',)\nthe cells: (,)\nfirst call: 1\nsecond call: 2\nthe cell now: 2\n" + "printed": "free variables: ('total',)\nthe cells: (,)\nfirst call: 1\nsecond call: 2\nthe cell now: 2\n" }, { "name": "t04-33", @@ -1534,7 +1588,7 @@ { "name": "t07-38", "status": "ok", - "printed": "asked twice, got the same object: True\nand it is still here after the call returned: \nits name: make_one\n" + "printed": "asked twice, got the same object: True\nand it is still here after the call returned: \nits name: make_one\n" }, { "name": "t07-40", @@ -1568,12 +1622,12 @@ { "name": "t08-11", "status": "ok", - "printed": "NoneType at 0x30d7d8, refcount is parked, this object is never freed, 8 bytes, not tracked\nint at 0x31a7cc, refcount is parked, this object is never freed, 16 bytes, not tracked\nstr at 0x13ce960, 3 reference(s), 26 bytes, not tracked\nlist at 0x1607360, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x1795a48, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x1450358, 4 reference(s), 84 bytes, tracked by the cycle collector\n" + "printed": "NoneType at 0x30d7d8, refcount is parked, this object is never freed, 8 bytes, not tracked\nint at 0x31a7cc, refcount is parked, this object is never freed, 16 bytes, not tracked\nstr at 0x1765610, 3 reference(s), 26 bytes, not tracked\nlist at 0x1772e00, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x158e610, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x1415bf8, 4 reference(s), 84 bytes, tracked by the cycle collector\n" }, { "name": "t08-14", "status": "ok", - "printed": "a == b True same contents\na is b False different objects\na is c True same object\n\nid(a) 0xfae948\nid(b) 0x1593148\nid(c) 0xfae948\n" + "printed": "a == b True same contents\na is b False different objects\na is c True same object\n\nid(a) 0x14869e8\nid(b) 0x17c63d8\nid(c) 0x14869e8\n" }, { "name": "t08-17", @@ -1613,7 +1667,7 @@ { "name": "t08-35", "status": "ok", - "printed": "dict at 0x13edeb8\ndict at 0x14fd060\nlist at 0x1593030\n" + "printed": "dict at 0x11c1d40\ndict at 0x125de30\nlist at 0x16fe6f8\n" }, { "name": "t08-37", @@ -1672,7 +1726,7 @@ { "name": "t09-21", "status": "ok", - "printed": "Node at 0x1852210 -> Node at 0x168ecd8 -> Node at 0x18c7da8 -> Node at 0x1852210\n" + "printed": "Node at 0x12cb060 -> Node at 0x1296770 -> Node at 0x16c2a08 -> Node at 0x12cb060\n" }, { "name": "t09-23", @@ -1702,7 +1756,7 @@ { "name": "t09-37", "status": "ok", - "printed": "first object was at 0x1131520\nsecond object is at 0x1131520\nsame address reused -> True\n" + "printed": "first object was at 0x1699c08\nsecond object is at 0x1699c08\nsame address reused -> True\n" } ] }, diff --git a/probes/pyodide/lessons.md b/probes/pyodide/lessons.md index 4b82a27..9895221 100644 --- a/probes/pyodide/lessons.md +++ b/probes/pyodide/lessons.md @@ -2,7 +2,7 @@ Generated by `just build-probe`. Do not edit by hand, the change will be overwritten. -32 lesson(s) on Pyodide 3.14.2: 32 ran end to end, 339 cell(s) in total. +33 lesson(s) on Pyodide 3.14.2: 33 ran end to end, 349 cell(s) in total. The checks in `report.md` next to this ask whether a surface exists. This runs the lessons themselves: every code cell of every notebook, in order, in one Pyodide runtime, with `pyxray` mounted off the disk rather than installed. The install cell is the one thing changed, and only its `%pip` line, which a reader in a browser does not need either. @@ -28,6 +28,7 @@ The checks in `report.md` next to this ask whether a surface exists. This runs t | o02-following-the-type-pointer | 9 | runs end to end | | o03-dunders-and-slots | 9 | runs end to end | | o04-the-order-things-are-found-in | 11 | runs end to end | +| o05-what-a-dot-does | 10 | runs end to end | | t01-one-line-seven-stages | 18 | runs end to end | | t02-text-becomes-tokens | 32 | runs end to end | | t03-tokens-become-a-tree | 13 | runs end to end | diff --git a/pyxray/src/pyxray/glossary.py b/pyxray/src/pyxray/glossary.py index 867b507..b317177 100644 --- a/pyxray/src/pyxray/glossary.py +++ b/pyxray/src/pyxray/glossary.py @@ -763,6 +763,15 @@ def anchor(name: str) -> str: see=("slot", "type object"), met="O03", ), + Term( + name="data descriptor", + short="An object on a type that has __set__ or __delete__ as well as __get__.", + long="The test is one line: `PyDescr_IsData` asks whether `tp_descr_set` is filled in. That one bit decides the whole precedence question, because attribute lookup calls a data descriptor before it ever looks in the instance dict and calls anything else after. `property` and the descriptors `__slots__` generates are data descriptors. A plain function is not, which is why you can shadow a method on one instance and cannot shadow a property.", + cite="Objects/descrobject.c:1028-1032@v3.15.0rc1#PyDescr_IsData", + also=("non data descriptor",), + see=("slot", "type object"), + met="O05", + ), Term( name="MRO", short="The flat list of classes, in order, that a name is looked up in.",