diff --git a/GLOSSARY.md b/GLOSSARY.md index 71c1310..151b33e 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) | [EXTENDED_ARG](#extended_arg) | [GC pre header](#gc-pre-header) | [JIT](#jit) | [PEG parser](#peg-parser) | [PyVarObject](#pyvarobject) | [Pyodide](#pyodide) | [WebAssembly](#webassembly) | [abstract syntax tree](#abstract-syntax-tree) | [adaptive instruction](#adaptive-instruction) | [arena](#arena) | [assembler](#assembler) | [backtrace](#backtrace) | [basic block](#basic-block) | [binding](#binding) | [block](#block) | [blurb](#blurb) | [borrowed reference](#borrowed-reference) | [bytecode](#bytecode) | [cell](#cell) | [class cell](#class-cell) | [closure](#closure) | [code generation](#code-generation) | [code object](#code-object) | [coding cookie](#coding-cookie) | [cold block](#cold-block) | [computed goto](#computed-goto) | [configure](#configure) | [constant folding](#constant-folding) | [control flow graph](#control-flow-graph) | [cycle collector](#cycle-collector) | [deallocation](#deallocation) | [debug build](#debug-build) | [deoptimization](#deoptimization) | [devguide](#devguide) | [dispatch](#dispatch) | [environment changed](#environment-changed) | [eval loop](#eval-loop) | [evaluation order](#evaluation-order) | [exception table](#exception-table) | [f string](#f-string) | [finalizer](#finalizer) | [frame](#frame) | [free threaded build](#free-threaded-build) | [free variable](#free-variable) | [gdb](#gdb) | [generated file](#generated-file) | [generation](#generation) | [grammar](#grammar) | [header file](#header-file) | [heap type](#heap-type) | [immortal object](#immortal-object) | [indent and dedent](#indent-and-dedent) | [inline cache](#inline-cache) | [instance dictionary](#instance-dictionary) | [instruction](#instruction) | [interning](#interning) | [left recursion](#left-recursion) | [line table](#line-table) | [magic number](#magic-number) | [marshal](#marshal) | [metaclass](#metaclass) | [monitoring events](#monitoring-events) | [new reference](#new-reference) | [object](#object) | [object header](#object-header) | [obmalloc](#obmalloc) | [oparg](#oparg) | [opcode](#opcode) | [parser generator](#parser-generator) | [pdb](#pdb) | [pointer](#pointer) | [pool](#pool) | [product type](#product-type) | [profile guided optimization](#profile-guided-optimization) | [pseudo instruction](#pseudo-instruction) | [pyc file](#pyc-file) | [pyconfig](#pyconfig) | [reference count](#reference-count) | [reference cycle](#reference-cycle) | [reference leak](#reference-leak) | [regen](#regen) | [regrtest](#regrtest) | [replacement field](#replacement-field) | [resource](#resource) | [scope](#scope) | [segmentation fault](#segmentation-fault) | [short circuiting](#short-circuiting) | [slot](#slot) | [slot wrapper](#slot-wrapper) | [small integer cache](#small-integer-cache) | [soft keyword](#soft-keyword) | [specialization](#specialization) | [stack depth](#stack-depth) | [stack effect](#stack-effect) | [static type](#static-type) | [stolen reference](#stolen-reference) | [struct](#struct) | [sum type](#sum-type) | [symbol table](#symbol-table) | [symbol table pass](#symbol-table-pass) | [t string](#t-string) | [test case](#test-case) | [tier one](#tier-one) | [tier two](#tier-two) | [token](#token) | [tokenizer](#tokenizer) | [trace function](#trace-function) | [type object](#type-object) | [underflow](#underflow) | [value stack](#value-stack) | [weak reference](#weak-reference) +[ASDL](#asdl) | [Argument Clinic](#argument-clinic) | [C3 linearization](#c3-linearization) | [EXTENDED_ARG](#extended_arg) | [GC pre header](#gc-pre-header) | [JIT](#jit) | [MRO](#mro) | [PEG parser](#peg-parser) | [PyVarObject](#pyvarobject) | [Pyodide](#pyodide) | [WebAssembly](#webassembly) | [abstract syntax tree](#abstract-syntax-tree) | [adaptive instruction](#adaptive-instruction) | [arena](#arena) | [assembler](#assembler) | [backtrace](#backtrace) | [basic block](#basic-block) | [binding](#binding) | [block](#block) | [blurb](#blurb) | [borrowed reference](#borrowed-reference) | [bytecode](#bytecode) | [cell](#cell) | [class cell](#class-cell) | [closure](#closure) | [code generation](#code-generation) | [code object](#code-object) | [coding cookie](#coding-cookie) | [cold block](#cold-block) | [computed goto](#computed-goto) | [configure](#configure) | [constant folding](#constant-folding) | [control flow graph](#control-flow-graph) | [cycle collector](#cycle-collector) | [deallocation](#deallocation) | [debug build](#debug-build) | [deoptimization](#deoptimization) | [devguide](#devguide) | [dispatch](#dispatch) | [environment changed](#environment-changed) | [eval loop](#eval-loop) | [evaluation order](#evaluation-order) | [exception table](#exception-table) | [f string](#f-string) | [finalizer](#finalizer) | [frame](#frame) | [free threaded build](#free-threaded-build) | [free variable](#free-variable) | [gdb](#gdb) | [generated file](#generated-file) | [generation](#generation) | [grammar](#grammar) | [header file](#header-file) | [heap type](#heap-type) | [immortal object](#immortal-object) | [indent and dedent](#indent-and-dedent) | [inline cache](#inline-cache) | [instance dictionary](#instance-dictionary) | [instruction](#instruction) | [interning](#interning) | [left recursion](#left-recursion) | [line table](#line-table) | [magic number](#magic-number) | [marshal](#marshal) | [metaclass](#metaclass) | [monitoring events](#monitoring-events) | [new reference](#new-reference) | [object](#object) | [object header](#object-header) | [obmalloc](#obmalloc) | [oparg](#oparg) | [opcode](#opcode) | [parser generator](#parser-generator) | [pdb](#pdb) | [pointer](#pointer) | [pool](#pool) | [product type](#product-type) | [profile guided optimization](#profile-guided-optimization) | [pseudo instruction](#pseudo-instruction) | [pyc file](#pyc-file) | [pyconfig](#pyconfig) | [reference count](#reference-count) | [reference cycle](#reference-cycle) | [reference leak](#reference-leak) | [regen](#regen) | [regrtest](#regrtest) | [replacement field](#replacement-field) | [resource](#resource) | [scope](#scope) | [segmentation fault](#segmentation-fault) | [short circuiting](#short-circuiting) | [slot](#slot) | [slot wrapper](#slot-wrapper) | [small integer cache](#small-integer-cache) | [soft keyword](#soft-keyword) | [specialization](#specialization) | [stack depth](#stack-depth) | [stack effect](#stack-effect) | [static type](#static-type) | [stolen reference](#stolen-reference) | [struct](#struct) | [sum type](#sum-type) | [symbol table](#symbol-table) | [symbol table pass](#symbol-table-pass) | [t string](#t-string) | [test case](#test-case) | [tier one](#tier-one) | [tier two](#tier-two) | [token](#token) | [tokenizer](#tokenizer) | [trace function](#trace-function) | [type object](#type-object) | [underflow](#underflow) | [value stack](#value-stack) | [weak reference](#weak-reference) ## Reading the source @@ -658,6 +658,22 @@ When a type written in C is made ready, `add_operators` walks the slot table and Also written `wrapper_descriptor`. First met in O03. See also [slot](#slot), [type object](#type-object). In the source: [`Objects/typeobject.c:12456-12470@v3.15.0rc1#add_operators`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L12456-L12470). +### MRO + +**The flat list of classes, in order, that a name is looked up in.** + +Every type carries one as `tp_mro`, computed once when the class is made and recomputed for the whole subtree if `__bases__` is later assigned. It always starts with the type itself and ends with `object`. Attribute lookup, `super`, and the slot table all read this list rather than walking `__bases__`, which is why multiple inheritance has one answer instead of a search. + +Also written method resolution order, `__mro__`, `tp_mro`. First met in O04. See also [C3 linearization](#c3-linearization), [type object](#type-object). In the source: [`Objects/typeobject.c:3431-3451@v3.15.0rc1#mro_implementation_unlocked`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3431-L3451). + +### C3 linearization + +**The merge rule that turns a class and its bases into one ordered list.** + +Take the MRO of each base, add the declared bases tuple, and repeatedly take the first head that does not appear later in any of the lists. If no such head exists the merge fails and you get a TypeError instead of a class. CPython spells this out in `pmerge`, and it is about forty lines. The rule guarantees a class comes before its bases and that the order you declared bases in is preserved. + +Also written C3, the merge. First met in O04. See also [MRO](#mro). In the source: [`Objects/typeobject.c:3361-3400@v3.15.0rc1#pmerge`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3361-L3400). + ### static type **A type object written out as a C literal and compiled into the binary.** diff --git a/README.md b/README.md index de9417c..4fdc519 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ A lesson can also end with a boss fight, which is a problem the text does not so | O01 | [The header, byte by byte](lessons/o01-the-header-byte-by-byte/o01.ipynb) | The two fields everybody quotes, read out of your own interpreter with ctypes, and the first of them turning out to be three: a thirty two bit count, sixteen bits named and unused, and the flags that say whether an object can ever be freed, plus why an immortal starts in the middle of the range and where a length lives when a type has one | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o01-the-header-byte-by-byte/o01.ipynb) | | O02 | [Following the type pointer](lessons/o02-following-the-type-pointer/o02.ipynb) | The biggest struct in the interpreter, read from Python: what basicsize and itemsize say about an instance and why list breaks the arithmetic, the one flag bit behind cannot set attribute of immutable type, the minus one in tp_dictoffset that is a sentinel rather than an offset, the thirty two bytes reserved in front of every instance of a class you wrote, and the five steps a class statement actually compiles to | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o02-following-the-type-pointer/o02.ipynb) | | O03 | [Dunders and the slots behind them](lessons/o03-dunders-and-slots/o03.ipynb) | Where int.__add__ comes from when nobody wrote it, why setting __repr__ on an instance does nothing and setting it on a class works an hour later, how one __getitem__ makes a class iterable without an __iter__, why a subclass __radd__ beats a base class __add__, and the three steps between defining __eq__ and getting unhashable type | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o03-dunders-and-slots/o03.ipynb) | +| O04 | [The order things are found in](lessons/o04-the-order-things-are-found-in/o04.ipynb) | Why a class with two bases carries a flat list instead of searching, the twenty line merge that builds it and matches CPython exactly on classes out of the standard library, the two ways a class statement can fail before the class exists, why super goes somewhere the class it is written in has never heard of, and the two ways the list changes after the fact | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o04-the-order-things-are-found-in/o04.ipynb) | More are landing in order. [lessons/README.md](lessons/README.md) explains how one is put together and how to run them locally. diff --git a/citations.lock.json b/citations.lock.json index 473f3e0..e45076a 100644 --- a/citations.lock.json +++ b/citations.lock.json @@ -740,11 +740,71 @@ "first_line": "add_operators(PyTypeObject *type)", "lines": 15 }, + "Objects/typeobject.c:12586-12614@v3.15.0rc1": { + "digest": "965f0e77a7064b66", + "first_line": "PyObject *", + "lines": 29 + }, + "Objects/typeobject.c:1278-1298@v3.15.0rc1": { + "digest": "e2e21d98952ebb7b", + "first_line": "static void", + "lines": 21 + }, + "Objects/typeobject.c:1798-1810@v3.15.0rc1": { + "digest": "d8630aee772bf39e", + "first_line": "mro_hierarchy_for_complete_type(PyTypeObject *type, PyObject *temp)", + "lines": 13 + }, "Objects/typeobject.c:2512-2531@v3.15.0rc1": { "digest": "9ac3593478808a3d", "first_line": "_PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems)", "lines": 20 }, + "Objects/typeobject.c:3246-3256@v3.15.0rc1": { + "digest": "73c31df6c43bee0c", + "first_line": "tail_contains(PyObject *tuple, Py_ssize_t whence, PyObject *o)", + "lines": 11 + }, + "Objects/typeobject.c:3270-3298@v3.15.0rc1": { + "digest": "2ec88f6f44bd8a6a", + "first_line": "check_duplicates(PyObject *tuple)", + "lines": 29 + }, + "Objects/typeobject.c:3309-3331@v3.15.0rc1": { + "digest": "2bb1be8360636ab0", + "first_line": "static void", + "lines": 23 + }, + "Objects/typeobject.c:3361-3400@v3.15.0rc1": { + "digest": "913155b71541437c", + "first_line": "pmerge(PyObject *acc, PyObject **to_merge, Py_ssize_t to_merge_size)", + "lines": 40 + }, + "Objects/typeobject.c:3431-3451@v3.15.0rc1": { + "digest": "087ba3b7f6fbf993", + "first_line": "mro_implementation_unlocked(PyTypeObject *type)", + "lines": 21 + }, + "Objects/typeobject.c:3453-3473@v3.15.0rc1": { + "digest": "7b880cb6bfeaf0a1", + "first_line": "if (n == 1) {", + "lines": 21 + }, + "Objects/typeobject.c:3480-3510@v3.15.0rc1": { + "digest": "8cb3a4a8df0d5132", + "first_line": "/* Find a superclass linearization that honors the constraints", + "lines": 31 + }, + "Objects/typeobject.c:3590-3609@v3.15.0rc1": { + "digest": "31f3540815b1fe10", + "first_line": "static PyObject *", + "lines": 20 + }, + "Objects/typeobject.c:6144-6180@v3.15.0rc1": { + "digest": "a16a8c9e914d75f2", + "first_line": "static int", + "lines": 37 + }, "Objects/typeobject.c:7290-7295@v3.15.0rc1": { "digest": "e3fd1c7921a386a3", "first_line": "PyTypeObject PyType_Type = {", diff --git a/lessons/CLAIMS.md b/lessons/CLAIMS.md index ae6c6a0..115dd24 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. -263 claims across 31 lessons, 21 of them not observable from Python. +272 claims across 32 lessons, 21 of them not observable from Python. ## B01. Building CPython, and whether you need to @@ -218,6 +218,20 @@ Without it the exception becomes the rule and this goes back to being a book. | a subclass that defines __radd__ itself is called before the base class __add__, and an unrelated class defining __radd__ is not, because both names share one slot and the dispatcher checks the subclass relationship first | [`o03-17`](o03-dunders-and-slots/o03.ipynb) | | a class that defines __eq__ and not __hash__ ends up with a real None stored under __hash__ in its class dict, and putting a hash back is a one line assignment | [`o03-19`](o03-dunders-and-slots/o03.ipynb) | +## O04. The order things are found in + +| Claim | Proved by | +| --- | --- | +| a class with two bases carries a flat __mro__ tuple that flattens both paths into one order, and method lookup follows that order rather than searching the base classes | [`o04-07`](o04-the-order-things-are-found-in/o04.ipynb) | +| swapping the order of the declared bases changes the resulting MRO, so the bases tuple is an input to the computation and not just a record of what you typed | [`o04-09`](o04-the-order-things-are-found-in/o04.ipynb) | +| every class's MRO starts with the class itself, ends with object, and lists each declared base in the order it was declared | [`o04-11`](o04-the-order-things-are-found-in/o04.ipynb) | +| a twenty line C3 merge written in Python reproduces CPython's __mro__ exactly, for hand written diamonds and for classes taken from the standard library | [`o04-13`](o04-the-order-things-are-found-in/o04.ipynb) | +| a chain of single inheritance produces an MRO that is just each class prepended to its base's MRO, which is the fast path CPython takes without running the merge | [`o04-15`](o04-the-order-things-are-found-in/o04.ipynb) | +| a pair of bases that order two classes in opposite ways makes the class statement itself raise TypeError, and a repeated base is rejected earlier by a separate check with a different message | [`o04-17`](o04-the-order-things-are-found-in/o04.ipynb) | +| super in a method resolves against the MRO of the instance's type, so an unchanged method in Left can dispatch to Right when the instance is a Both, even though Left never refers to Right | [`o04-19`](o04-the-order-things-are-found-in/o04.ipynb) | +| assigning to a class's __bases__ recomputes the MRO of that class and of every subclass, and instances that already exist pick up the new method at once | [`o04-22`](o04-the-order-things-are-found-in/o04.ipynb) | +| a metaclass that overrides mro can return an order the C3 rules would never produce, and attribute lookup uses that order without complaint | [`o04-24`](o04-the-order-things-are-found-in/o04.ipynb) | + ## T01. One line, seven stages | Claim | Proved by | diff --git a/lessons/README.md b/lessons/README.md index 935a127..88462b3 100644 --- a/lessons/README.md +++ b/lessons/README.md @@ -35,6 +35,7 @@ Each lesson is a notebook you can run. There is nothing to install and nothing t | [O01. The header, byte by byte](o01-the-header-byte-by-byte/o01.ipynb) | Reading the object header out of memory with ctypes and checking every field against something Python will tell you another way, the thirty two bit count and the two fields sharing its word, why an immortal object parks at 3 << 30 rather than at the top, the two flag bits that say how it got there, why a string CPython uses itself is immortal and yours is not, and the two words in front of the header that only the collector sees | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o01-the-header-byte-by-byte/o01.ipynb) | | [O02. Following the type pointer](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) | ## The three programs diff --git a/lessons/o04-the-order-things-are-found-in/build.py b/lessons/o04-the-order-things-are-found-in/build.py new file mode 100644 index 0000000..354759c --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/build.py @@ -0,0 +1,499 @@ +#!/usr/bin/env python +"""O04. The order things are found in. + +The fourth lesson of the object model part. O03 explained which dictionary a name is found in. +This one explains the order those dictionaries get looked at, which is the only interesting +question once a class has more than one base. + +The answer is a list, computed once when the class is made and stored on the type. It is not a +search. There is a forty line function called `pmerge` that builds it, and the whole of multiple +inheritance in Python is that function plus the fact that `super` reads the list of the instance +rather than the list of the class it appears in. + +The lesson writes the merge out in Python, checks it against `__mro__` for a pile of classes +including some from the standard library, and then shows the two ways it can fail. + +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("o04-the-order-things-are-found-in", "o04") +badge = lesson.badge +cite = lesson.cite +term = lesson.term +figure = Diagrams("o04-the-order-things-are-found-in").figure + + +lesson.md(f""" +# O04. The order things are found in + +{badge} + +O03 said a slot dispatcher looks a dunder name up on the type, and that a class inherits slots from its base. With one base that is easy to picture. Go up one link, look in that dict, keep going until you run out. + +With two bases there is no obvious next link. + +{figure("the-diamond", "the four classes, and the two paths from D up to A")} + +D inherits from B and from C, and both of those inherit from A. A defines `where`, C also defines `where`, B does not. So `D().where()` has two paths up the tree and they disagree. + +Python does not search. It computes one flat list per class, once, and reads it front to back. This lesson is about how that list gets built, why it sometimes cannot be built at all, and why `super` is not what people assume. +""") + + +lesson.md(""" +## About the source references + +Now and then this lesson points at CPython's own source, like this: `Objects/typeobject.c:3361-3400@v3.15.0rc1#pmerge`. + +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. The two agree on every line of output in this lesson. +""") + + +lesson.code( + """ +import pyxray + +pyxray.show() +""", + differs=BANNER, + quiet=True, +) + + +lesson.md(f""" +## Two bases and one question + +Here is the diamond as code. B and C both inherit from A, D inherits from both, and A and C each define a `where` method. + +The list D actually uses is on the type, under `__mro__`. It is a tuple of classes, it is computed when the class statement runs, and it is what the interpreter reads. `__bases__` is what you declared. `__mro__` is what that turned into. + +{lesson.claim("a class with two bases carries a flat __mro__ tuple that flattens both paths into one order, and method lookup follows that order rather than searching the base classes")} +""") + + +lesson.code( + """ +class A: + def where(self): + return "A" + + +class B(A): + pass + + +class C(A): + def where(self): + return "C" + + +class D(B, C): + pass + + +print(f" D.__bases__ {[c.__name__ for c in D.__bases__]}") +print(f" D.__mro__ {[c.__name__ for c in D.__mro__]}") +print(f" D().where() {D().where()}") +""" +) + + +lesson.md(f""" +The answer is C, and A is last, even though A is where `where` was defined first and B is the base you wrote first. + +The order you write the bases in is not decoration. It is a constraint the list has to satisfy, and swapping the two bases gives you a different list and a different method. + +{lesson.claim("swapping the order of the declared bases changes the resulting MRO, so the bases tuple is an input to the computation and not just a record of what you typed")} +""") + + +lesson.code( + """ +class BC(B, C): + pass + + +class CB(C, B): + pass + + +print(f" class BC(B, C) {[c.__name__ for c in BC.__mro__]}") +print(f" class CB(C, B) {[c.__name__ for c in CB.__mro__]}") +""" +) + + +lesson.md(f""" +## The rules the order has to obey + +The {term("MRO")} is not picked out of a hat. Three things have to hold, and they hold for every class in the list rather than only for the one you asked about. + +{figure("three-rules", "the three constraints every MRO has to satisfy")} + +First, a class comes before all of its bases. Second, the bases stay in the order they were declared. Third, both of those apply to every entry in the list, which is the part that makes the whole thing hard, because a constraint that B introduces can rule out an order that looked fine from D. + +There is one more thing that always holds and that nobody has to enforce, because it falls out of the first two: `object` is last. Every class ends up with it somewhere in its bases, and nothing comes after it. + +{lesson.claim("every class's MRO starts with the class itself, ends with object, and lists each declared base in the order it was declared")} +""") + + +lesson.code( + """ +import collections +import io + +for cls in [D, BC, int, io.StringIO, collections.OrderedDict]: + mro = cls.__mro__ + positions = [mro.index(b) for b in cls.__bases__] + print( + f" {cls.__name__:13} first is {mro[0].__name__ == cls.__name__!s:5}" + f" last is {mro[-1].__name__:9}" + f" bases at {positions}, in order {positions == sorted(positions)}" + ) +""" +) + + +lesson.md(f""" +## Doing the merge by hand + +The algorithm has a name, {term("C3 linearization")}, and a paper behind it, but the code is short enough to fit on a screen. In CPython it is `pmerge`, {cite("Objects/typeobject.c:3361-3400@v3.15.0rc1#pmerge")}. + +{figure("merging-the-lists", "the merge picking one class at a time for the diamond")} + +It takes several lists. For a class with bases B and C, the lists are B's MRO, C's MRO, and the declared bases tuple `(B, C)` on the end, {cite("Objects/typeobject.c:3480-3510@v3.15.0rc1#pmerge")}. Then it does one thing over and over: look at the front of each list, and take the first one that does not appear anywhere in the tail of any list. + +That test is a helper of its own, `tail_contains`, {cite("Objects/typeobject.c:3246-3256@v3.15.0rc1#tail_contains")}, and it is the whole rule. If a class shows up later in some list, taking it now would put it in front of something that has to come first, so it waits. + +When a class is taken it gets appended to the answer and every list that had it at the front moves along by one. Then the scan starts again from the top. If a full pass finds nothing to take and the lists are not all empty, the merge is stuck, and that is a different story further down. + +Here it is in Python, next to the real thing. `c3` builds a class's order from its bases' orders the same way CPython does, and the assertion is what makes this worth reading: it matches `__mro__` exactly, for classes from this notebook and for classes from the standard library. + +{lesson.claim("a twenty line C3 merge written in Python reproduces CPython's __mro__ exactly, for hand written diamonds and for classes taken from the standard library")} +""") + + +lesson.code( + """ +def merge(sequences): + \"\"\"Take the first head that does not appear in any other list's tail.\"\"\" + result = [] + sequences = [list(s) for s in sequences if s] + while sequences: + for sequence in sequences: + head = sequence[0] + if not any(head in s[1:] for s in sequences): + break + else: + raise TypeError("no consistent order exists") + result.append(head) + for s in sequences: + if s[0] is head: + del s[0] + sequences = [s for s in sequences if s] + return result + + +def c3(cls): + \"\"\"A class, then the merge of its bases' orders and the bases tuple itself.\"\"\" + lists = [c3(base) for base in cls.__bases__] + lists.append(list(cls.__bases__)) + return [cls, *merge(lists)] + + +checked = [D, BC, CB, B, C, A, bool, io.StringIO, collections.OrderedDict, Exception, type] +for cls in checked: + assert c3(cls) == list(cls.__mro__), cls.__name__ + +print(f" matched __mro__ exactly for all {len(checked)} of them") +print(f" c3(D) {[c.__name__ for c in c3(D)]}") +""" +) + + +lesson.md(f""" +That is a real reimplementation of the interesting part, and it took twenty lines. The C version is longer mostly because it has to manage memory and because it keeps an index into each list instead of deleting from the front. + +CPython also has a shortcut. If there is exactly one base there is nothing to merge, so it skips straight to putting the new class in front of the base's order, {cite("Objects/typeobject.c:3453-3473@v3.15.0rc1")}. Single inheritance never runs the merge at all, which is worth knowing when you are wondering what a class statement costs. + +{lesson.claim("a chain of single inheritance produces an MRO that is just each class prepended to its base's MRO, which is the fast path CPython takes without running the merge")} +""") + + +lesson.code( + """ +chain = object +for level in range(5): + chain = type(f"Link{level}", (chain,), {}) + +print(f" {[c.__name__ for c in chain.__mro__]}") +print(" each step only put the new class in front of the one below it") +""" +) + + +lesson.md(f""" +## When there is no such order + +Sometimes the constraints contradict each other and no list satisfies all of them. The merge notices, because a whole pass finds no candidate while lists still have things in them. + +{figure("no-order-exists", "two classes that each make sense and one combination that cannot")} + +`class XY(X, Y)` says X comes before Y. `class YX(Y, X)` says the opposite. Each is fine alone. A class inheriting from both would need both at once, so there is no order, and instead of quietly picking one CPython refuses to make the class. + +The message comes from `set_mro_error`, {cite("Objects/typeobject.c:3309-3331@v3.15.0rc1#set_mro_error")}, which collects the classes sitting at the front of the stuck lists and names them. Those are the candidates that were competing, which is usually enough to see what you did. + +There is a second, simpler failure checked before the merge even starts. Listing the same base twice is not a contradiction, it is just meaningless, and `check_duplicates` catches it first, {cite("Objects/typeobject.c:3270-3298@v3.15.0rc1#check_duplicates")}. + +{lesson.claim("a pair of bases that order two classes in opposite ways makes the class statement itself raise TypeError, and a repeated base is rejected earlier by a separate check with a different message")} +""") + + +lesson.code( + """ +class X: + pass + + +class Y: + pass + + +class XY(X, Y): + pass + + +class YX(Y, X): + pass + + +try: + + class Impossible(XY, YX): + pass + +except TypeError as error: + print(f" class Impossible(XY, YX) TypeError: {error}") + +try: + + class Twice(X, X): + pass + +except TypeError as error: + print(f" class Twice(X, X) TypeError: {error}") +""" +) + + +lesson.md(f""" +Notice which classes got named in the first message. Not `XY` and `YX`, which are the bases you wrote, but `X` and `Y`, which are the two the merge could not choose between. + +## super does not mean the base class + +This is the part of the MRO that has real consequences for code people write, and the usual mental model of it is wrong. + +{figure("super-follows-the-instance", "super reading the instance's MRO rather than the class it was written in")} + +`super()` inside a method of `Left` does not mean "Left's base class". It means "whatever comes after Left in the MRO of this instance". The lookup is `_PySuper_LookupDescr`, {cite("Objects/typeobject.c:12586-12614@v3.15.0rc1#_PySuper_LookupDescr")}, and the first thing it does is fetch the MRO of the object's type, not of the class the call was written in. Then it finds the class in that list and starts from the entry after it. + +So the same line of code, in the same method, goes somewhere different depending on what you called it on. `Left` does not know `Right` exists, and cannot know, because `Right` may be written years later. + +{lesson.claim("super in a method resolves against the MRO of the instance's type, so an unchanged method in Left can dispatch to Right when the instance is a Both, even though Left never refers to Right")} +""") + + +lesson.code( + """ +class Base: + def who(self): + return ["Base"] + + +class Left(Base): + def who(self): + return ["Left", *super().who()] + + +class Right(Base): + def who(self): + return ["Right", *super().who()] + + +class Both(Left, Right): + def who(self): + return ["Both", *super().who()] + + +print(f" Left().who() {Left().who()}") +print(f" Both().who() {Both().who()}") +print(f" Both.__mro__ {[c.__name__ for c in Both.__mro__]}") +""" +) + + +lesson.md(""" +The same `super().who()` inside `Left` returned `Base` for one instance and `Right` for the other. That is the whole of what people mean by cooperative multiple inheritance, and it is one list lookup. + +It also explains the rule that everything in such a chain has to call `super()`. If `Left.who` returned early instead, `Right.who` would never run, because nothing else is walking the list. The list is only walked by the chain of `super` calls themselves. + +## The list is not frozen + +Two things can change it after the class exists. + +Assigning to `__bases__` recomputes the MRO, and not just for that class. `mro_hierarchy_for_complete_type` walks every subclass and rebuilds each one, because their orders were derived from this one and are now wrong. +""") + + +lesson.md(f""" +{cite("Objects/typeobject.c:1798-1810@v3.15.0rc1#mro_hierarchy_for_complete_type")} is the function. Existing instances see the change immediately, since they hold a pointer to the type and the type holds the list. + +{lesson.claim("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")} +""") + + +lesson.code( + """ +class Old: + def tag(self): + return "Old" + + +class New: + def tag(self): + return "New" + + +class Thing(Old): + pass + + +class Sub(Thing): + pass + + +existing = Sub() +print(f" before Sub().tag() {existing.tag()} {[c.__name__ for c in Sub.__mro__]}") + +Thing.__bases__ = (New,) + +print(f" after Sub().tag() {existing.tag()} {[c.__name__ for c in Sub.__mro__]}") +""" +) + + +lesson.md(f""" +The other way in is a metaclass. Making a class ready calls `mro_invoke`, {cite("Objects/typeobject.c:3590-3609@v3.15.0rc1#mro_invoke")}, which checks whether the metaclass is plain `type`. If it is not, it calls the metaclass's `mro()` method instead of running the merge, and whatever comes back is the list, subject to a sanity check. + +So the C3 rules are the default, not the law. A metaclass can hand back any order it likes and the interpreter will use it. + +There is a cost. `type_mro_modified` turns off the type version tag for a class with a custom `mro()`, {cite("Objects/typeobject.c:1278-1298@v3.15.0rc1#type_mro_modified")}, which takes it out of the method cache that makes normal attribute lookup fast. + +{lesson.claim("a metaclass that overrides mro can return an order the C3 rules would never produce, and attribute lookup uses that order without complaint")} +""") + + +lesson.code( + """ +class Reversed(type): + def mro(cls): + return [cls, *reversed(type.mro(cls)[1:-1]), object] + + +class P: + def tag(self): + return "P" + + +class Q(P): + def tag(self): + return "Q" + + +class Odd(Q, metaclass=Reversed): + pass + + +print(f" Odd.__mro__ {[c.__name__ for c in Odd.__mro__]}") +print(f" Odd().tag() {Odd().tag()}") +print(" P comes before Q, which is backwards, and the lookup follows it anyway") +""" +) + + +lesson.md(f""" +## Where a lookup actually goes + +Now the loop that uses all of this. It is `find_name_in_mro`, {cite("Objects/typeobject.c:6144-6180@v3.15.0rc1#find_name_in_mro")}, and it is about as simple as it sounds: take the type's MRO tuple, walk it front to back, do a dict lookup in each class's dict, and stop at the first hit. + +{figure("where-a-lookup-goes", "one list, walked front to back, one dict lookup per entry")} + +No tree walking, no backtracking, no work about inheritance at all. All of that happened once, when the class was created. This is the payoff for computing the list up front, and it is why `__mro__` exists as a stored tuple rather than as a method that figures it out each time. + +O05 picks the loop apart properly, including the cache that usually means it does not run at all. +""") + + +lesson.md(""" +## Try it yourself + +Three things to poke at. + +Print the MROs of a few classes with genuinely deep hierarchies and see how long they get. `collections.OrderedDict`, `io.TextIOWrapper` and any exception class are good starting points. Then run `c3` from this notebook on them and confirm it still matches. + +Take the diamond and give B a `where` method too. The answer changes, and you can predict it from `D.__mro__` before you run it. Then change the declared order to `class D(C, B)` and predict again. + +Build a class hierarchy where the merge fails and the error message names classes you did not write in the failing class statement. Three levels is enough. The message lists the stuck heads, which are often further up than the bases you were looking at, and getting a feel for that is what makes the error readable. + +## What just happened + +Every class carries a flat list of classes called its MRO, computed once when the class is made, stored on the type, and read front to back by every attribute lookup. + +The list is built by a merge. Take the MRO of each base, add the declared bases tuple, and repeatedly take the first head that does not appear in any other list's tail. Twenty lines of Python reproduce it exactly, including for classes out of the standard library. Single inheritance skips the merge entirely and just prepends. + +The rules the list satisfies are that a class comes before its bases, that declared order is preserved, and that both hold for every entry rather than only the first. Sometimes no list satisfies them, and then the class statement raises `TypeError` and names the classes the merge was stuck between. A repeated base is caught before the merge by a separate check. + +`super()` reads the MRO of the instance, not of the class it was written in, which is why a method in one class can dispatch to a class it has never heard of and why every link in such a chain has to call `super()` for the chain to continue. + +The list is not permanent. Assigning `__bases__` recomputes it for the class and everything under it, and a metaclass with its own `mro()` can hand back any order at all, at the cost of the method cache. + +## What is next + +O05 is attribute lookup, the loop that reads the list this lesson built. Getting `x.y` involves more than the MRO: there is the instance dict, there is `__getattr__` as a fallback, there is the type attribute cache that makes most lookups skip the walk completely, and there is the fact that looking a name up on a class and on an instance take genuinely different paths through the code. +""") + + +raise SystemExit(lesson.save()) diff --git a/lessons/o04-the-order-things-are-found-in/diagrams.py b/lessons/o04-the-order-things-are-found-in/diagrams.py new file mode 100644 index 0000000..bbeefac --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +"""The diagrams for O04, the order the interpreter looks names up in. + +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 `merging-the-lists`. The MRO is not a search, it is a list +computed once by a forty line merge, and every rule people learn about multiple inheritance +falls out of how that merge picks its next candidate. +""" + +from nbdiagram import Gallery, figures + +gallery = Gallery("o04-the-order-things-are-found-in") + +gallery.add( + figures.table( + "the-diamond", + ["class", "declared bases", "defines where"], + [ + ["A", "object", "yes"], + ["B", "A", "no"], + ["C", "A", "yes"], + ["D", "B, C", "no"], + ], + title="Four classes, and one question: whose where does D get", + caption="Both B and C lead back to A, so there are two paths up and something has to pick.", + tones=["quiet", "quiet", "focus", "focus"], + ) +) + + +gallery.add( + figures.stack( + "three-rules", + [ + "a class comes before every one of its bases", + "the bases stay in the order you declared them", + "and the same holds for every class in the list, not just D", + ], + title="What the order has to satisfy", + note="There is usually only one list that satisfies all three, and sometimes there is none.", + ) +) + + +gallery.add( + figures.flow( + "merging-the-lists", + [ + "three lists: B A object, C A object, and B C", + "B is at the front of one and in nobody's tail, take B", + "A is next in line but it sits in C's tail, so skip it", + "C is in no tail either, take C, and now A is free", + "D, B, C, A, object", + ], + title="The merge, one step at a time, for the diamond", + labels=[ + "the two base MROs plus the bases you declared", + "this is the whole rule, applied over and over", + "taking A here would put it before C, which is wrong", + ], + tones=["input", "focus", "warning", "focus", "durable"], + ) +) + + +gallery.add( + figures.compare( + "no-order-exists", + ( + "class XY(X, Y)", + [ + "says X comes before Y", + "and that is fine on its own", + ], + ), + ( + "class YX(Y, X)", + [ + "says Y comes before X", + "and that is fine on its own", + ], + ), + title="Two classes that each make sense, and one that cannot", + verdict="class Bad(XY, YX) needs both at once, so pmerge gets stuck and raises TypeError.", + verdict_tone="warning", + ) +) + + +gallery.add( + figures.flow( + "super-follows-the-instance", + [ + "super().who() inside Left", + "look at the instance, not at Left", + "find Left in that instance's MRO", + "take whatever is next in the list", + "which may be a class Left never heard of", + ], + title="Why super is not a shortcut for the base class", + labels=[ + "type(self), which Left cannot know in advance", + "so the answer changes per instance", + "one step, no search", + ], + tones=["input", "focus", "intermediate", "focus", "durable"], + ) +) + + +gallery.add( + figures.flow( + "where-a-lookup-goes", + [ + "x.method in your code", + "find_name_in_mro walks type(x).__mro__", + "checking each class dict in turn", + "and stops at the first hit", + ], + title="One list, walked front to back, and that is the whole search", + labels=[ + "the list is already built, nothing is computed here", + "a plain dict lookup per entry", + ], + tones=["input", "focus", "intermediate", "durable"], + ) +) + + +raise SystemExit(gallery.save()) diff --git a/lessons/o04-the-order-things-are-found-in/diagrams/merging-the-lists.excalidraw b/lessons/o04-the-order-things-are-found-in/diagrams/merging-the-lists.excalidraw new file mode 100644 index 0000000..e39b0b5 --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/merging-the-lists.excalidraw @@ -0,0 +1,746 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "3ffe5e88349d42ce1d4f", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 628.92, + "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 merge, one step at a time, for the diamond", + "originalText": "The merge, one step at a time, for the diamond", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "be5a429f73685cef3573", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 605.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": "6c22b08d5c625c8b28a1", + "type": "text" + }, + { + "id": "2d2ee55623b90b892be1", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "6c22b08d5c625c8b28a1", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 573.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": "three lists: B A object, C A object, and B C", + "originalText": "three lists: B A object, C A object, and B C", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "be5a429f73685cef3573", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "19e21799704cccf66744", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 605.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": "98536af7757f152b54bf", + "type": "text" + }, + { + "id": "2d2ee55623b90b892be1", + "type": "arrow" + }, + { + "id": "6494b7f317564f0f12c2", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "98536af7757f152b54bf", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 573.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": "B is at the front of one and in nobody's tail, take B", + "originalText": "B is at the front of one and in nobody's tail, take B", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "19e21799704cccf66744", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "7041d7c08d62776cc6f9", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 605.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": "bd2efb26eb56f2fd1273", + "type": "text" + }, + { + "id": "6494b7f317564f0f12c2", + "type": "arrow" + }, + { + "id": "d979b545757dfd960553", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "bd2efb26eb56f2fd1273", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 573.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 is next in line but it sits in C's tail, so skip it", + "originalText": "A is next in line but it sits in C's tail, so skip it", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "7041d7c08d62776cc6f9", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "3c8fe0b02f9b07e8669c", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 605.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": "730e6a1b2d35cc50a236", + "type": "text" + }, + { + "id": "d979b545757dfd960553", + "type": "arrow" + }, + { + "id": "175bca56329f1435aec2", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "730e6a1b2d35cc50a236", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 573.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": "C is in no tail either, take C, and now A is free", + "originalText": "C is in no tail either, take C, and now A is free", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3c8fe0b02f9b07e8669c", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "a9488a28afd7a30895e0", + "type": "rectangle", + "x": 0.0, + "y": 570.0, + "width": 605.0, + "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": "bff7c5962faa6db95dc3", + "type": "text" + }, + { + "id": "175bca56329f1435aec2", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "bff7c5962faa6db95dc3", + "type": "text", + "x": 16.0, + "y": 592.5, + "width": 573.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": "D, B, C, A, object", + "originalText": "D, B, C, A, object", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "a9488a28afd7a30895e0", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "2d2ee55623b90b892be1", + "type": "arrow", + "x": 302.5, + "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": "be5a429f73685cef3573", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "19e21799704cccf66744", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "d182ce95477bff50962d", + "type": "text", + "x": 314.5, + "y": 140.0, + "width": 421.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 two base MROs plus the bases you declared", + "originalText": "the two base MROs plus the bases you declared", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "6494b7f317564f0f12c2", + "type": "arrow", + "x": 302.5, + "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": "19e21799704cccf66744", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "7041d7c08d62776cc6f9", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "aa21afabab4337d5c781", + "type": "text", + "x": 314.5, + "y": 270.0, + "width": 394.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": "this is the whole rule, applied over and over", + "originalText": "this is the whole rule, applied over and over", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d979b545757dfd960553", + "type": "arrow", + "x": 302.5, + "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": "7041d7c08d62776cc6f9", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "3c8fe0b02f9b07e8669c", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "ad1ffa4370f5b718055f", + "type": "text", + "x": 314.5, + "y": 400.0, + "width": 460.08, + "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": "taking A here would put it before C, which is wrong", + "originalText": "taking A here would put it before C, which is wrong", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "175bca56329f1435aec2", + "type": "arrow", + "x": 302.5, + "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": "3c8fe0b02f9b07e8669c", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "a9488a28afd7a30895e0", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o04-the-order-things-are-found-in/diagrams/merging-the-lists.svg b/lessons/o04-the-order-things-are-found-in/diagrams/merging-the-lists.svg new file mode 100644 index 0000000..5c3a658 --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/merging-the-lists.svg @@ -0,0 +1 @@ +The merge, one step at a time, for the diamondthree lists: B A object, C A object, and B CB is at the front of one and in nobody's tail, take BA is next in line but it sits in C's tail, so skip itC is in no tail either, take C, and now A is freeD, B, C, A, objectthe two base MROs plus the bases you declaredthis is the whole rule, applied over and overtaking A here would put it before C, which is wrong diff --git a/lessons/o04-the-order-things-are-found-in/diagrams/no-order-exists.excalidraw b/lessons/o04-the-order-things-are-found-in/diagrams/no-order-exists.excalidraw new file mode 100644 index 0000000..c208e74 --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/no-order-exists.excalidraw @@ -0,0 +1,473 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "380c9c60c5f27e8e9728", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 742.6800000000001, + "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 classes that each make sense, and one that cannot", + "originalText": "Two classes that each make sense, and one that cannot", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "471195409cabc999da26", + "type": "text", + "x": 103.0, + "y": 50.0, + "width": 150.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": "class XY(X, Y)", + "originalText": "class XY(X, Y)", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "c698c7866787a36b1133", + "type": "rectangle", + "x": 0.0, + "y": 87.0, + "width": 356.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": "1a427add619600c19c0c", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "1a427add619600c19c0c", + "type": "text", + "x": 16.0, + "y": 103.0, + "width": 324.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": "says X comes before Y", + "originalText": "says X comes before Y", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "c698c7866787a36b1133", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "0db77c493bec23b8f0bb", + "type": "rectangle", + "x": 0.0, + "y": 147.0, + "width": 356.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": "39c4ab0450f9923ada6e", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "39c4ab0450f9923ada6e", + "type": "text", + "x": 16.0, + "y": 163.0, + "width": 324.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 that is fine on its own", + "originalText": "and that is fine on its own", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "0db77c493bec23b8f0bb", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "0bef715693e4b611c4cb", + "type": "text", + "x": 519.0, + "y": 50.0, + "width": 150.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": "class YX(Y, X)", + "originalText": "class YX(Y, X)", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7c2710d8d1ec3a08ee74", + "type": "rectangle", + "x": 416.0, + "y": 87.0, + "width": 356.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": "0130ae13411ea93a462d", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "0130ae13411ea93a462d", + "type": "text", + "x": 432.0, + "y": 103.0, + "width": 324.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": "says Y comes before X", + "originalText": "says Y comes before X", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "7c2710d8d1ec3a08ee74", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "47117f67002d9492be97", + "type": "rectangle", + "x": 416.0, + "y": 147.0, + "width": 356.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": "ca9d4447e9c6ea2e4b27", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "ca9d4447e9c6ea2e4b27", + "type": "text", + "x": 432.0, + "y": 163.0, + "width": 324.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 that is fine on its own", + "originalText": "and that is fine on its own", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "47117f67002d9492be97", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "116a6517dfbdaf36779d", + "type": "rectangle", + "x": 0.0, + "y": 224.0, + "width": 772.0, + "height": 82.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": "c4c1d0b6cbf03d120c47", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "c4c1d0b6cbf03d120c47", + "type": "text", + "x": 16.0, + "y": 240.0, + "width": 740.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": "class Bad(XY, YX) needs both at once, so pmerge gets stuck and\nraises TypeError.", + "originalText": "class Bad(XY, YX) needs both at once, so pmerge gets stuck and\nraises TypeError.", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "116a6517dfbdaf36779d", + "lineHeight": 1.25, + "autoResize": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o04-the-order-things-are-found-in/diagrams/no-order-exists.svg b/lessons/o04-the-order-things-are-found-in/diagrams/no-order-exists.svg new file mode 100644 index 0000000..5913d94 --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/no-order-exists.svg @@ -0,0 +1 @@ +Two classes that each make sense, and one that cannotclass XY(X, Y)says X comes before Yand that is fine on its ownclass YX(Y, X)says Y comes before Xand that is fine on its ownclass Bad(XY, YX) needs both at once, so pmerge gets stuck andraises TypeError. diff --git a/lessons/o04-the-order-things-are-found-in/diagrams/super-follows-the-instance.excalidraw b/lessons/o04-the-order-things-are-found-in/diagrams/super-follows-the-instance.excalidraw new file mode 100644 index 0000000..827c62f --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/super-follows-the-instance.excalidraw @@ -0,0 +1,746 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "11e4374590b9b1b382ad", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 617.04, + "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": "Why super is not a shortcut for the base class", + "originalText": "Why super is not a shortcut for the base class", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7d921c6babb7fe942707", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 494.8999999999999, + "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": "cd3c8d90c341acfc98e3", + "type": "text" + }, + { + "id": "86a2a45072d4beba647d", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "cd3c8d90c341acfc98e3", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 462.8999999999999, + "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": "super().who() inside Left", + "originalText": "super().who() inside Left", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "7d921c6babb7fe942707", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "08af2d67a4625caffc3b", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 494.8999999999999, + "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": "33e5fe61bef3004e4af7", + "type": "text" + }, + { + "id": "86a2a45072d4beba647d", + "type": "arrow" + }, + { + "id": "f7ad154a146db5b9e1a6", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "33e5fe61bef3004e4af7", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 462.8999999999999, + "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": "look at the instance, not at Left", + "originalText": "look at the instance, not at Left", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "08af2d67a4625caffc3b", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c705845ad6dd62d0c52b", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 494.8999999999999, + "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": "4c0fb53a4e9bd0008640", + "type": "text" + }, + { + "id": "f7ad154a146db5b9e1a6", + "type": "arrow" + }, + { + "id": "9da0bd4655ddaea461fe", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "4c0fb53a4e9bd0008640", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 462.8999999999999, + "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": "find Left in that instance's MRO", + "originalText": "find Left in that instance's MRO", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c705845ad6dd62d0c52b", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "eb9fdc2ad65ba0b30e29", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 494.8999999999999, + "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": "06b3026173e39ce3c5ba", + "type": "text" + }, + { + "id": "9da0bd4655ddaea461fe", + "type": "arrow" + }, + { + "id": "7aac284ad2fe3aa5f35b", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "06b3026173e39ce3c5ba", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 462.8999999999999, + "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": "take whatever is next in the list", + "originalText": "take whatever is next in the list", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "eb9fdc2ad65ba0b30e29", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "0e8f11947e4e3af5978b", + "type": "rectangle", + "x": 0.0, + "y": 570.0, + "width": 494.8999999999999, + "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": "765f72980590e0d21b5d", + "type": "text" + }, + { + "id": "7aac284ad2fe3aa5f35b", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "765f72980590e0d21b5d", + "type": "text", + "x": 16.0, + "y": 592.5, + "width": 462.8999999999999, + "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": "which may be a class Left never heard of", + "originalText": "which may be a class Left never heard of", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "0e8f11947e4e3af5978b", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "86a2a45072d4beba647d", + "type": "arrow", + "x": 247.44999999999996, + "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": "7d921c6babb7fe942707", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "08af2d67a4625caffc3b", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "abafe49318fd22265c0a", + "type": "text", + "x": 259.44999999999993, + "y": 140.0, + "width": 403.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": "type(self), which Left cannot know in advance", + "originalText": "type(self), which Left cannot know in advance", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f7ad154a146db5b9e1a6", + "type": "arrow", + "x": 247.44999999999996, + "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": "08af2d67a4625caffc3b", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "c705845ad6dd62d0c52b", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "e1653fd275c089798c3e", + "type": "text", + "x": 259.44999999999993, + "y": 270.0, + "width": 314.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": "so the answer changes per instance", + "originalText": "so the answer changes per instance", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "9da0bd4655ddaea461fe", + "type": "arrow", + "x": 247.44999999999996, + "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": "c705845ad6dd62d0c52b", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "eb9fdc2ad65ba0b30e29", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "19f2b21195fbc0bf8cc3", + "type": "text", + "x": 259.44999999999993, + "y": 400.0, + "width": 171.6, + "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 step, no search", + "originalText": "one step, no search", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7aac284ad2fe3aa5f35b", + "type": "arrow", + "x": 247.44999999999996, + "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": "eb9fdc2ad65ba0b30e29", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "0e8f11947e4e3af5978b", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o04-the-order-things-are-found-in/diagrams/super-follows-the-instance.svg b/lessons/o04-the-order-things-are-found-in/diagrams/super-follows-the-instance.svg new file mode 100644 index 0000000..dc8a675 --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/super-follows-the-instance.svg @@ -0,0 +1 @@ +Why super is not a shortcut for the base classsuper().who() inside Leftlook at the instance, not at Leftfind Left in that instance's MROtake whatever is next in the listwhich may be a class Left never heard oftype(self), which Left cannot know in advanceso the answer changes per instanceone step, no search diff --git a/lessons/o04-the-order-things-are-found-in/diagrams/the-diamond.excalidraw b/lessons/o04-the-order-things-are-found-in/diagrams/the-diamond.excalidraw new file mode 100644 index 0000000..dae2e42 --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/the-diamond.excalidraw @@ -0,0 +1,1035 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "4f2f4826d8ffa482ed54", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 751.6800000000001, + "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": "Four classes, and one question: whose where does D get", + "originalText": "Four classes, and one question: whose where does D get", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e39eada3e1266063ce86", + "type": "text", + "x": 14.0, + "y": 50.0, + "width": 44.4, + "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": "class", + "originalText": "class", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "93367eef42e37c85b9a3", + "type": "text", + "x": 90.0, + "y": 50.0, + "width": 127.19999999999999, + "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": "declared bases", + "originalText": "declared bases", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "ceb7f4510aacc5ca6ee4", + "type": "text", + "x": 252.4, + "y": 50.0, + "width": 119.75999999999999, + "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": "defines where", + "originalText": "defines where", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7c5e60d762c8b38757fa", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 391.20000000000005, + "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 + ], + [ + 391.20000000000005, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "e735fe5731dd52b9f76c", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 391.20000000000005, + "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 + ], + [ + 391.20000000000005, + 0.0 + ], + [ + 391.20000000000005, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "fe8de630abdfa729a8d5", + "type": "text", + "x": 14.0, + "y": 86.0, + "width": 9.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", + "originalText": "A", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a4ad7944fbce2c355806", + "type": "text", + "x": 90.0, + "y": 86.0, + "width": 57.599999999999994, + "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": "object", + "originalText": "object", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "220f8fde015dc3b79550", + "type": "text", + "x": 252.4, + "y": 86.0, + "width": 28.799999999999997, + "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": "yes", + "originalText": "yes", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "8c75355ddd173abf6353", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 391.20000000000005, + "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 + ], + [ + 391.20000000000005, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "255e37d6955bc45ecb95", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 391.20000000000005, + "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 + ], + [ + 391.20000000000005, + 0.0 + ], + [ + 391.20000000000005, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "a0eda551d63ba7222613", + "type": "text", + "x": 14.0, + "y": 126.0, + "width": 9.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": "B", + "originalText": "B", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "25ae0d9569bc68dbbeae", + "type": "text", + "x": 90.0, + "y": 126.0, + "width": 9.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", + "originalText": "A", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "6fdbe30096323e900536", + "type": "text", + "x": 252.4, + "y": 126.0, + "width": 19.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": "no", + "originalText": "no", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e83df24c307984e773d3", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 391.20000000000005, + "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 + ], + [ + 391.20000000000005, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "4d0bf58b5c0f8d44e46d", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 391.20000000000005, + "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 + ], + [ + 391.20000000000005, + 0.0 + ], + [ + 391.20000000000005, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "dc4c11cf04073b04f2da", + "type": "text", + "x": 14.0, + "y": 166.0, + "width": 9.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": "C", + "originalText": "C", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e19d1e584561fdea089a", + "type": "text", + "x": 90.0, + "y": 166.0, + "width": 9.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", + "originalText": "A", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f25329e48bdfe11eeefd", + "type": "text", + "x": 252.4, + "y": 166.0, + "width": 28.799999999999997, + "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": "yes", + "originalText": "yes", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f26a8458da310b26cb2f", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 391.20000000000005, + "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 + ], + [ + 391.20000000000005, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "c4e8eec8c830ed18da12", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 391.20000000000005, + "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 + ], + [ + 391.20000000000005, + 0.0 + ], + [ + 391.20000000000005, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "67eaab6a4dcf51c9f4ab", + "type": "text", + "x": 14.0, + "y": 206.0, + "width": 9.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": "D", + "originalText": "D", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "b4419847d6e90dfe1f91", + "type": "text", + "x": 90.0, + "y": 206.0, + "width": 38.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": "B, C", + "originalText": "B, C", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "62d278bbf5e6c10333ac", + "type": "text", + "x": 252.4, + "y": 206.0, + "width": 19.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": "no", + "originalText": "no", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "4d8b732007bc4158fdcf", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 391.20000000000005, + "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 + ], + [ + 391.20000000000005, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "fa86105facac6da5e1a9", + "type": "text", + "x": 0.0, + "y": 256.0, + "width": 738.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": "Both B and C lead back to A, so there are two paths up and something has to pick.", + "originalText": "Both B and C lead back to A, so there are two paths up and something has to pick.", + "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/o04-the-order-things-are-found-in/diagrams/the-diamond.svg b/lessons/o04-the-order-things-are-found-in/diagrams/the-diamond.svg new file mode 100644 index 0000000..6975a01 --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/the-diamond.svg @@ -0,0 +1 @@ +Four classes, and one question: whose where does D getclassdeclared basesdefines whereAobjectyesBAnoCAyesDB, CnoBoth B and C lead back to A, so there are two paths up and something has to pick. diff --git a/lessons/o04-the-order-things-are-found-in/diagrams/three-rules.excalidraw b/lessons/o04-the-order-things-are-found-in/diagrams/three-rules.excalidraw new file mode 100644 index 0000000..ed5fc5f --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/three-rules.excalidraw @@ -0,0 +1,333 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "25c2ff89340f2bf5d037", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 383.04, + "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 the order has to satisfy", + "originalText": "What the order has to satisfy", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a5bcefb624065d0ea225", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 728.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": "4113024c882c3a06df65", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "4113024c882c3a06df65", + "type": "text", + "x": 16.0, + "y": 66.0, + "width": 696.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 the same holds for every class in the list, not just D", + "originalText": "and the same holds for every class in the list, not just D", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "a5bcefb624065d0ea225", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "68085bf9fb31c574be3a", + "type": "text", + "x": 744.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": "da2da737860c8754f2f1", + "type": "rectangle", + "x": 0.0, + "y": 102.0, + "width": 728.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": "b599fc773d41c5d9a9e4", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "b599fc773d41c5d9a9e4", + "type": "text", + "x": 16.0, + "y": 118.0, + "width": 696.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 bases stay in the order you declared them", + "originalText": "the bases stay in the order you declared them", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "da2da737860c8754f2f1", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "f4497a957cd0a0662d1a", + "type": "rectangle", + "x": 0.0, + "y": 154.0, + "width": 728.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": "f45bc8f05fc8f60be85a", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "f45bc8f05fc8f60be85a", + "type": "text", + "x": 16.0, + "y": 170.0, + "width": 696.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 class comes before every one of its bases", + "originalText": "a class comes before every one of its bases", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "f4497a957cd0a0662d1a", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "d73085fe3cf25dff1c01", + "type": "text", + "x": 0.0, + "y": 226.0, + "width": 737.52, + "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 usually only one list that satisfies all three, and sometimes there is none.", + "originalText": "There is usually only one list that satisfies all three, and sometimes there is none.", + "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/o04-the-order-things-are-found-in/diagrams/three-rules.svg b/lessons/o04-the-order-things-are-found-in/diagrams/three-rules.svg new file mode 100644 index 0000000..f95c63c --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/three-rules.svg @@ -0,0 +1 @@ +What the order has to satisfyand the same holds for every class in the list, not just Dtopthe bases stay in the order you declared thema class comes before every one of its basesThere is usually only one list that satisfies all three, and sometimes there is none. diff --git a/lessons/o04-the-order-things-are-found-in/diagrams/where-a-lookup-goes.excalidraw b/lessons/o04-the-order-things-are-found-in/diagrams/where-a-lookup-goes.excalidraw new file mode 100644 index 0000000..b5cb3ed --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/where-a-lookup-goes.excalidraw @@ -0,0 +1,580 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "05c34621a2a2dac0966e", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 794.8799999999999, + "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": "One list, walked front to back, and that is the whole search", + "originalText": "One list, walked front to back, and that is the whole search", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "66ced82624c9a5e5b8d7", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 471.79999999999995, + "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": "258f2070dee4f62adb31", + "type": "text" + }, + { + "id": "1f430cf5e1da01bb1efb", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "258f2070dee4f62adb31", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 439.79999999999995, + "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.method in your code", + "originalText": "x.method in your code", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "66ced82624c9a5e5b8d7", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "de5b7c2bcee601c9fe52", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 471.79999999999995, + "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": "ed0c367f33580f141c7f", + "type": "text" + }, + { + "id": "1f430cf5e1da01bb1efb", + "type": "arrow" + }, + { + "id": "2f0c03256f1c8a2d84e4", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "ed0c367f33580f141c7f", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 439.79999999999995, + "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": "find_name_in_mro walks type(x).__mro__", + "originalText": "find_name_in_mro walks type(x).__mro__", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "de5b7c2bcee601c9fe52", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "838663a0d48c3b9142c5", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 471.79999999999995, + "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": "bce1eed7ddd796158261", + "type": "text" + }, + { + "id": "2f0c03256f1c8a2d84e4", + "type": "arrow" + }, + { + "id": "ae6543aa27fa24be8f44", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "bce1eed7ddd796158261", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 439.79999999999995, + "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": "checking each class dict in turn", + "originalText": "checking each class dict in turn", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "838663a0d48c3b9142c5", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "de5dd9d8fc7cf8bc6a2e", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 471.79999999999995, + "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": "08a7bdb3beb053ee5327", + "type": "text" + }, + { + "id": "ae6543aa27fa24be8f44", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "08a7bdb3beb053ee5327", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 439.79999999999995, + "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 stops at the first hit", + "originalText": "and stops at the first hit", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "de5dd9d8fc7cf8bc6a2e", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "1f430cf5e1da01bb1efb", + "type": "arrow", + "x": 235.89999999999998, + "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": "66ced82624c9a5e5b8d7", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "de5b7c2bcee601c9fe52", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "06bbc512da0cae3ddf8d", + "type": "text", + "x": 247.89999999999998, + "y": 140.0, + "width": 437.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": "the list is already built, nothing is computed here", + "originalText": "the list is already built, nothing is computed here", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2f0c03256f1c8a2d84e4", + "type": "arrow", + "x": 235.89999999999998, + "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": "de5b7c2bcee601c9fe52", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "838663a0d48c3b9142c5", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "c0077af2cb2f9ffa31af", + "type": "text", + "x": 247.89999999999998, + "y": 270.0, + "width": 249.6, + "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 dict lookup per entry", + "originalText": "a plain dict lookup per entry", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "ae6543aa27fa24be8f44", + "type": "arrow", + "x": 235.89999999999998, + "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": "838663a0d48c3b9142c5", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "de5dd9d8fc7cf8bc6a2e", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o04-the-order-things-are-found-in/diagrams/where-a-lookup-goes.svg b/lessons/o04-the-order-things-are-found-in/diagrams/where-a-lookup-goes.svg new file mode 100644 index 0000000..86c9498 --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/diagrams/where-a-lookup-goes.svg @@ -0,0 +1 @@ +One list, walked front to back, and that is the whole searchx.method in your codefind_name_in_mro walks type(x).__mro__checking each class dict in turnand stops at the first hitthe list is already built, nothing is computed herea plain dict lookup per entry diff --git a/lessons/o04-the-order-things-are-found-in/o04.ipynb b/lessons/o04-the-order-things-are-found-in/o04.ipynb new file mode 100644 index 0000000..8cd1815 --- /dev/null +++ b/lessons/o04-the-order-things-are-found-in/o04.ipynb @@ -0,0 +1,570 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "o04-01", + "metadata": {}, + "source": [ + "# O04. The order things are found in\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/o04-the-order-things-are-found-in/o04.ipynb)\n", + "\n", + "O03 said a slot dispatcher looks a dunder name up on the type, and that a class inherits slots from its base. With one base that is easy to picture. Go up one link, look in that dict, keep going until you run out.\n", + "\n", + "With two bases there is no obvious next link.\n", + "\n", + "![the four classes, and the two paths from D up to A](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o04-the-order-things-are-found-in/diagrams/the-diamond.svg)\n", + "\n", + "D inherits from B and from C, and both of those inherit from A. A defines `where`, C also defines `where`, B does not. So `D().where()` has two paths up the tree and they disagree.\n", + "\n", + "Python does not search. It computes one flat list per class, once, and reads it front to back. This lesson is about how that list gets built, why it sometimes cannot be built at all, and why `super` is not what people assume." + ] + }, + { + "cell_type": "markdown", + "id": "o04-02", + "metadata": {}, + "source": [ + "## About the source references\n", + "\n", + "Now and then this lesson points at CPython's own source, like this: `Objects/typeobject.c:3361-3400@v3.15.0rc1#pmerge`.\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": "o04-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": "o04-04", + "metadata": {}, + "source": [ + "## Which Python is this\n", + "\n", + "Everything below was checked against the version this cell prints and against 3.14. The two agree on every line of output in this lesson." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o04-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": "o04-06", + "metadata": {}, + "source": [ + "## Two bases and one question\n", + "\n", + "Here is the diamond as code. B and C both inherit from A, D inherits from both, and A and C each define a `where` method.\n", + "\n", + "The list D actually uses is on the type, under `__mro__`. It is a tuple of classes, it is computed when the class statement runs, and it is what the interpreter reads. `__bases__` is what you declared. `__mro__` is what that turned into.\n", + "\n", + "a class with two bases carries a flat __mro__ tuple that flattens both paths into one order, and method lookup follows that order rather than searching the base classes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o04-07", + "metadata": {}, + "outputs": [], + "source": [ + "class A:\n", + " def where(self):\n", + " return \"A\"\n", + "\n", + "\n", + "class B(A):\n", + " pass\n", + "\n", + "\n", + "class C(A):\n", + " def where(self):\n", + " return \"C\"\n", + "\n", + "\n", + "class D(B, C):\n", + " pass\n", + "\n", + "\n", + "print(f\" D.__bases__ {[c.__name__ for c in D.__bases__]}\")\n", + "print(f\" D.__mro__ {[c.__name__ for c in D.__mro__]}\")\n", + "print(f\" D().where() {D().where()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o04-08", + "metadata": {}, + "source": [ + "The answer is C, and A is last, even though A is where `where` was defined first and B is the base you wrote first.\n", + "\n", + "The order you write the bases in is not decoration. It is a constraint the list has to satisfy, and swapping the two bases gives you a different list and a different method.\n", + "\n", + "swapping the order of the declared bases changes the resulting MRO, so the bases tuple is an input to the computation and not just a record of what you typed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o04-09", + "metadata": {}, + "outputs": [], + "source": [ + "class BC(B, C):\n", + " pass\n", + "\n", + "\n", + "class CB(C, B):\n", + " pass\n", + "\n", + "\n", + "print(f\" class BC(B, C) {[c.__name__ for c in BC.__mro__]}\")\n", + "print(f\" class CB(C, B) {[c.__name__ for c in CB.__mro__]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o04-10", + "metadata": {}, + "source": [ + "## The rules the order has to obey\n", + "\n", + "The [MRO](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#mro) is not picked out of a hat. Three things have to hold, and they hold for every class in the list rather than only for the one you asked about.\n", + "\n", + "![the three constraints every MRO has to satisfy](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o04-the-order-things-are-found-in/diagrams/three-rules.svg)\n", + "\n", + "First, a class comes before all of its bases. Second, the bases stay in the order they were declared. Third, both of those apply to every entry in the list, which is the part that makes the whole thing hard, because a constraint that B introduces can rule out an order that looked fine from D.\n", + "\n", + "There is one more thing that always holds and that nobody has to enforce, because it falls out of the first two: `object` is last. Every class ends up with it somewhere in its bases, and nothing comes after it.\n", + "\n", + "every class's MRO starts with the class itself, ends with object, and lists each declared base in the order it was declared" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o04-11", + "metadata": {}, + "outputs": [], + "source": [ + "import collections\n", + "import io\n", + "\n", + "for cls in [D, BC, int, io.StringIO, collections.OrderedDict]:\n", + " mro = cls.__mro__\n", + " positions = [mro.index(b) for b in cls.__bases__]\n", + " print(\n", + " f\" {cls.__name__:13} first is {mro[0].__name__ == cls.__name__!s:5}\"\n", + " f\" last is {mro[-1].__name__:9}\"\n", + " f\" bases at {positions}, in order {positions == sorted(positions)}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "o04-12", + "metadata": {}, + "source": [ + "## Doing the merge by hand\n", + "\n", + "The algorithm has a name, [C3 linearization](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#c3-linearization), and a paper behind it, but the code is short enough to fit on a screen. In CPython it is `pmerge`, [Objects/typeobject.c:3361-3400@v3.15.0rc1#pmerge](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3361-L3400).\n", + "\n", + "![the merge picking one class at a time for the diamond](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o04-the-order-things-are-found-in/diagrams/merging-the-lists.svg)\n", + "\n", + "It takes several lists. For a class with bases B and C, the lists are B's MRO, C's MRO, and the declared bases tuple `(B, C)` on the end, [Objects/typeobject.c:3480-3510@v3.15.0rc1#pmerge](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3480-L3510). Then it does one thing over and over: look at the front of each list, and take the first one that does not appear anywhere in the tail of any list.\n", + "\n", + "That test is a helper of its own, `tail_contains`, [Objects/typeobject.c:3246-3256@v3.15.0rc1#tail_contains](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3246-L3256), and it is the whole rule. If a class shows up later in some list, taking it now would put it in front of something that has to come first, so it waits.\n", + "\n", + "When a class is taken it gets appended to the answer and every list that had it at the front moves along by one. Then the scan starts again from the top. If a full pass finds nothing to take and the lists are not all empty, the merge is stuck, and that is a different story further down.\n", + "\n", + "Here it is in Python, next to the real thing. `c3` builds a class's order from its bases' orders the same way CPython does, and the assertion is what makes this worth reading: it matches `__mro__` exactly, for classes from this notebook and for classes from the standard library.\n", + "\n", + "a twenty line C3 merge written in Python reproduces CPython's __mro__ exactly, for hand written diamonds and for classes taken from the standard library" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o04-13", + "metadata": {}, + "outputs": [], + "source": [ + "def merge(sequences):\n", + " \"\"\"Take the first head that does not appear in any other list's tail.\"\"\"\n", + " result = []\n", + " sequences = [list(s) for s in sequences if s]\n", + " while sequences:\n", + " for sequence in sequences:\n", + " head = sequence[0]\n", + " if not any(head in s[1:] for s in sequences):\n", + " break\n", + " else:\n", + " raise TypeError(\"no consistent order exists\")\n", + " result.append(head)\n", + " for s in sequences:\n", + " if s[0] is head:\n", + " del s[0]\n", + " sequences = [s for s in sequences if s]\n", + " return result\n", + "\n", + "\n", + "def c3(cls):\n", + " \"\"\"A class, then the merge of its bases' orders and the bases tuple itself.\"\"\"\n", + " lists = [c3(base) for base in cls.__bases__]\n", + " lists.append(list(cls.__bases__))\n", + " return [cls, *merge(lists)]\n", + "\n", + "\n", + "checked = [D, BC, CB, B, C, A, bool, io.StringIO, collections.OrderedDict, Exception, type]\n", + "for cls in checked:\n", + " assert c3(cls) == list(cls.__mro__), cls.__name__\n", + "\n", + "print(f\" matched __mro__ exactly for all {len(checked)} of them\")\n", + "print(f\" c3(D) {[c.__name__ for c in c3(D)]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o04-14", + "metadata": {}, + "source": [ + "That is a real reimplementation of the interesting part, and it took twenty lines. The C version is longer mostly because it has to manage memory and because it keeps an index into each list instead of deleting from the front.\n", + "\n", + "CPython also has a shortcut. If there is exactly one base there is nothing to merge, so it skips straight to putting the new class in front of the base's order, [Objects/typeobject.c:3453-3473@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3453-L3473). Single inheritance never runs the merge at all, which is worth knowing when you are wondering what a class statement costs.\n", + "\n", + "a chain of single inheritance produces an MRO that is just each class prepended to its base's MRO, which is the fast path CPython takes without running the merge" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o04-15", + "metadata": {}, + "outputs": [], + "source": [ + "chain = object\n", + "for level in range(5):\n", + " chain = type(f\"Link{level}\", (chain,), {})\n", + "\n", + "print(f\" {[c.__name__ for c in chain.__mro__]}\")\n", + "print(\" each step only put the new class in front of the one below it\")" + ] + }, + { + "cell_type": "markdown", + "id": "o04-16", + "metadata": {}, + "source": [ + "## When there is no such order\n", + "\n", + "Sometimes the constraints contradict each other and no list satisfies all of them. The merge notices, because a whole pass finds no candidate while lists still have things in them.\n", + "\n", + "![two classes that each make sense and one combination that cannot](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o04-the-order-things-are-found-in/diagrams/no-order-exists.svg)\n", + "\n", + "`class XY(X, Y)` says X comes before Y. `class YX(Y, X)` says the opposite. Each is fine alone. A class inheriting from both would need both at once, so there is no order, and instead of quietly picking one CPython refuses to make the class.\n", + "\n", + "The message comes from `set_mro_error`, [Objects/typeobject.c:3309-3331@v3.15.0rc1#set_mro_error](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3309-L3331), which collects the classes sitting at the front of the stuck lists and names them. Those are the candidates that were competing, which is usually enough to see what you did.\n", + "\n", + "There is a second, simpler failure checked before the merge even starts. Listing the same base twice is not a contradiction, it is just meaningless, and `check_duplicates` catches it first, [Objects/typeobject.c:3270-3298@v3.15.0rc1#check_duplicates](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3270-L3298).\n", + "\n", + "a pair of bases that order two classes in opposite ways makes the class statement itself raise TypeError, and a repeated base is rejected earlier by a separate check with a different message" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o04-17", + "metadata": {}, + "outputs": [], + "source": [ + "class X:\n", + " pass\n", + "\n", + "\n", + "class Y:\n", + " pass\n", + "\n", + "\n", + "class XY(X, Y):\n", + " pass\n", + "\n", + "\n", + "class YX(Y, X):\n", + " pass\n", + "\n", + "\n", + "try:\n", + "\n", + " class Impossible(XY, YX):\n", + " pass\n", + "\n", + "except TypeError as error:\n", + " print(f\" class Impossible(XY, YX) TypeError: {error}\")\n", + "\n", + "try:\n", + "\n", + " class Twice(X, X):\n", + " pass\n", + "\n", + "except TypeError as error:\n", + " print(f\" class Twice(X, X) TypeError: {error}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o04-18", + "metadata": {}, + "source": [ + "Notice which classes got named in the first message. Not `XY` and `YX`, which are the bases you wrote, but `X` and `Y`, which are the two the merge could not choose between.\n", + "\n", + "## super does not mean the base class\n", + "\n", + "This is the part of the MRO that has real consequences for code people write, and the usual mental model of it is wrong.\n", + "\n", + "![super reading the instance's MRO rather than the class it was written in](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o04-the-order-things-are-found-in/diagrams/super-follows-the-instance.svg)\n", + "\n", + "`super()` inside a method of `Left` does not mean \"Left's base class\". It means \"whatever comes after Left in the MRO of this instance\". The lookup is `_PySuper_LookupDescr`, [Objects/typeobject.c:12586-12614@v3.15.0rc1#_PySuper_LookupDescr](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L12586-L12614), and the first thing it does is fetch the MRO of the object's type, not of the class the call was written in. Then it finds the class in that list and starts from the entry after it.\n", + "\n", + "So the same line of code, in the same method, goes somewhere different depending on what you called it on. `Left` does not know `Right` exists, and cannot know, because `Right` may be written years later.\n", + "\n", + "super in a method resolves against the MRO of the instance's type, so an unchanged method in Left can dispatch to Right when the instance is a Both, even though Left never refers to Right" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o04-19", + "metadata": {}, + "outputs": [], + "source": [ + "class Base:\n", + " def who(self):\n", + " return [\"Base\"]\n", + "\n", + "\n", + "class Left(Base):\n", + " def who(self):\n", + " return [\"Left\", *super().who()]\n", + "\n", + "\n", + "class Right(Base):\n", + " def who(self):\n", + " return [\"Right\", *super().who()]\n", + "\n", + "\n", + "class Both(Left, Right):\n", + " def who(self):\n", + " return [\"Both\", *super().who()]\n", + "\n", + "\n", + "print(f\" Left().who() {Left().who()}\")\n", + "print(f\" Both().who() {Both().who()}\")\n", + "print(f\" Both.__mro__ {[c.__name__ for c in Both.__mro__]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o04-20", + "metadata": {}, + "source": [ + "The same `super().who()` inside `Left` returned `Base` for one instance and `Right` for the other. That is the whole of what people mean by cooperative multiple inheritance, and it is one list lookup.\n", + "\n", + "It also explains the rule that everything in such a chain has to call `super()`. If `Left.who` returned early instead, `Right.who` would never run, because nothing else is walking the list. The list is only walked by the chain of `super` calls themselves.\n", + "\n", + "## The list is not frozen\n", + "\n", + "Two things can change it after the class exists.\n", + "\n", + "Assigning to `__bases__` recomputes the MRO, and not just for that class. `mro_hierarchy_for_complete_type` walks every subclass and rebuilds each one, because their orders were derived from this one and are now wrong." + ] + }, + { + "cell_type": "markdown", + "id": "o04-21", + "metadata": {}, + "source": [ + "[Objects/typeobject.c:1798-1810@v3.15.0rc1#mro_hierarchy_for_complete_type](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L1798-L1810) is the function. Existing instances see the change immediately, since they hold a pointer to the type and the type holds the list.\n", + "\n", + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o04-22", + "metadata": {}, + "outputs": [], + "source": [ + "class Old:\n", + " def tag(self):\n", + " return \"Old\"\n", + "\n", + "\n", + "class New:\n", + " def tag(self):\n", + " return \"New\"\n", + "\n", + "\n", + "class Thing(Old):\n", + " pass\n", + "\n", + "\n", + "class Sub(Thing):\n", + " pass\n", + "\n", + "\n", + "existing = Sub()\n", + "print(f\" before Sub().tag() {existing.tag()} {[c.__name__ for c in Sub.__mro__]}\")\n", + "\n", + "Thing.__bases__ = (New,)\n", + "\n", + "print(f\" after Sub().tag() {existing.tag()} {[c.__name__ for c in Sub.__mro__]}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o04-23", + "metadata": {}, + "source": [ + "The other way in is a metaclass. Making a class ready calls `mro_invoke`, [Objects/typeobject.c:3590-3609@v3.15.0rc1#mro_invoke](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L3590-L3609), which checks whether the metaclass is plain `type`. If it is not, it calls the metaclass's `mro()` method instead of running the merge, and whatever comes back is the list, subject to a sanity check.\n", + "\n", + "So the C3 rules are the default, not the law. A metaclass can hand back any order it likes and the interpreter will use it.\n", + "\n", + "There is a cost. `type_mro_modified` turns off the type version tag for a class with a custom `mro()`, [Objects/typeobject.c:1278-1298@v3.15.0rc1#type_mro_modified](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L1278-L1298), which takes it out of the method cache that makes normal attribute lookup fast.\n", + "\n", + "a metaclass that overrides mro can return an order the C3 rules would never produce, and attribute lookup uses that order without complaint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o04-24", + "metadata": {}, + "outputs": [], + "source": [ + "class Reversed(type):\n", + " def mro(cls):\n", + " return [cls, *reversed(type.mro(cls)[1:-1]), object]\n", + "\n", + "\n", + "class P:\n", + " def tag(self):\n", + " return \"P\"\n", + "\n", + "\n", + "class Q(P):\n", + " def tag(self):\n", + " return \"Q\"\n", + "\n", + "\n", + "class Odd(Q, metaclass=Reversed):\n", + " pass\n", + "\n", + "\n", + "print(f\" Odd.__mro__ {[c.__name__ for c in Odd.__mro__]}\")\n", + "print(f\" Odd().tag() {Odd().tag()}\")\n", + "print(\" P comes before Q, which is backwards, and the lookup follows it anyway\")" + ] + }, + { + "cell_type": "markdown", + "id": "o04-25", + "metadata": {}, + "source": [ + "## Where a lookup actually goes\n", + "\n", + "Now the loop that uses all of this. It is `find_name_in_mro`, [Objects/typeobject.c:6144-6180@v3.15.0rc1#find_name_in_mro](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L6144-L6180), and it is about as simple as it sounds: take the type's MRO tuple, walk it front to back, do a dict lookup in each class's dict, and stop at the first hit.\n", + "\n", + "![one list, walked front to back, one dict lookup per entry](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o04-the-order-things-are-found-in/diagrams/where-a-lookup-goes.svg)\n", + "\n", + "No tree walking, no backtracking, no work about inheritance at all. All of that happened once, when the class was created. This is the payoff for computing the list up front, and it is why `__mro__` exists as a stored tuple rather than as a method that figures it out each time.\n", + "\n", + "O05 picks the loop apart properly, including the cache that usually means it does not run at all." + ] + }, + { + "cell_type": "markdown", + "id": "o04-26", + "metadata": {}, + "source": [ + "## Try it yourself\n", + "\n", + "Three things to poke at.\n", + "\n", + "Print the MROs of a few classes with genuinely deep hierarchies and see how long they get. `collections.OrderedDict`, `io.TextIOWrapper` and any exception class are good starting points. Then run `c3` from this notebook on them and confirm it still matches.\n", + "\n", + "Take the diamond and give B a `where` method too. The answer changes, and you can predict it from `D.__mro__` before you run it. Then change the declared order to `class D(C, B)` and predict again.\n", + "\n", + "Build a class hierarchy where the merge fails and the error message names classes you did not write in the failing class statement. Three levels is enough. The message lists the stuck heads, which are often further up than the bases you were looking at, and getting a feel for that is what makes the error readable.\n", + "\n", + "## What just happened\n", + "\n", + "Every class carries a flat list of classes called its MRO, computed once when the class is made, stored on the type, and read front to back by every attribute lookup.\n", + "\n", + "The list is built by a merge. Take the MRO of each base, add the declared bases tuple, and repeatedly take the first head that does not appear in any other list's tail. Twenty lines of Python reproduce it exactly, including for classes out of the standard library. Single inheritance skips the merge entirely and just prepends.\n", + "\n", + "The rules the list satisfies are that a class comes before its bases, that declared order is preserved, and that both hold for every entry rather than only the first. Sometimes no list satisfies them, and then the class statement raises `TypeError` and names the classes the merge was stuck between. A repeated base is caught before the merge by a separate check.\n", + "\n", + "`super()` reads the MRO of the instance, not of the class it was written in, which is why a method in one class can dispatch to a class it has never heard of and why every link in such a chain has to call `super()` for the chain to continue.\n", + "\n", + "The list is not permanent. Assigning `__bases__` recomputes it for the class and everything under it, and a metaclass with its own `mro()` can hand back any order at all, at the cost of the method cache.\n", + "\n", + "## What is next\n", + "\n", + "O05 is attribute lookup, the loop that reads the list this lesson built. Getting `x.y` involves more than the MRO: there is the instance dict, there is `__getattr__` as a fallback, there is the type attribute cache that makes most lookups skip the walk completely, and there is the fact that looking a name up on a class and on an instance take genuinely different paths through the code." + ] + } + ], + "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 ad41828..fe893f8 100644 --- a/probes/pyodide/lessons.json +++ b/probes/pyodide/lessons.json @@ -70,7 +70,7 @@ { "name": "b03-07", "status": "ok", - "printed": "test_adding (__main__.Arithmetic.test_adding) ... ok\ntest_dividing (__main__.Arithmetic.test_dividing) ... ok\ntest_powers (__main__.Arithmetic.test_powers) ... FAIL\n\n======================================================================\nFAIL: test_powers (__main__.Arithmetic.test_powers)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n File \"b03-07\", line 13, in test_powers\nAssertionError: 1024 != 1000\n\n----------------------------------------------------------------------\nRan 3 tests in 0.002s\n\nFAILED (failures=1)\n\n3 tests run, 1 failed, 0 errored\n" + "printed": "test_adding (__main__.Arithmetic.test_adding) ... ok\ntest_dividing (__main__.Arithmetic.test_dividing) ... ok\ntest_powers (__main__.Arithmetic.test_powers) ... FAIL\n\n======================================================================\nFAIL: test_powers (__main__.Arithmetic.test_powers)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n File \"b03-07\", line 13, in test_powers\nAssertionError: 1024 != 1000\n\n----------------------------------------------------------------------\nRan 3 tests in 0.001s\n\nFAILED (failures=1)\n\n3 tests run, 1 failed, 0 errored\n" }, { "name": "b03-10", @@ -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 c3 a5 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 49 a9 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 1788192195 source 1788192195\n size 54 source 54\n" + "printed": " magic 168627755\n flags 0\n mtime 1788193097 source 1788193097\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: 1788192060\n imported it with no complaint: 'hi world'\n and the header now says 1788192060\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: 1788193206\n imported it with no complaint: 'hi world'\n and the header now says 1788193206\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 22337768\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 24569584\n id(Thing) is 24569584\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 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" }, { "name": "o01-10", @@ -816,6 +816,65 @@ } ] }, + { + "slug": "o04-the-order-things-are-found-in", + "cells": [ + { + "name": "o04-03", + "status": "ok" + }, + { + "name": "o04-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": "o04-07", + "status": "ok", + "printed": " D.__bases__ ['B', 'C']\n D.__mro__ ['D', 'B', 'C', 'A', 'object']\n D().where() C\n" + }, + { + "name": "o04-09", + "status": "ok", + "printed": " class BC(B, C) ['BC', 'B', 'C', 'A', 'object']\n class CB(C, B) ['CB', 'C', 'B', 'A', 'object']\n" + }, + { + "name": "o04-11", + "status": "ok", + "printed": " D first is True last is object bases at [1, 2], in order True\n BC first is True last is object bases at [1, 2], in order True\n int first is True last is object bases at [1], in order True\n StringIO first is True last is object bases at [1], in order True\n OrderedDict first is True last is object bases at [1], in order True\n" + }, + { + "name": "o04-13", + "status": "ok", + "printed": " matched __mro__ exactly for all 11 of them\n c3(D) ['D', 'B', 'C', 'A', 'object']\n" + }, + { + "name": "o04-15", + "status": "ok", + "printed": " ['Link4', 'Link3', 'Link2', 'Link1', 'Link0', 'object']\n each step only put the new class in front of the one below it\n" + }, + { + "name": "o04-17", + "status": "ok", + "printed": " class Impossible(XY, YX) TypeError: Cannot create a consistent method resolution order (MRO) for bases X, Y\n class Twice(X, X) TypeError: duplicate base class X\n" + }, + { + "name": "o04-19", + "status": "ok", + "printed": " Left().who() ['Left', 'Base']\n Both().who() ['Both', 'Left', 'Right', 'Base']\n Both.__mro__ ['Both', 'Left', 'Right', 'Base', 'object']\n" + }, + { + "name": "o04-22", + "status": "ok", + "printed": " before Sub().tag() Old ['Sub', 'Thing', 'Old', 'object']\n after Sub().tag() New ['Sub', 'Thing', 'New', 'object']\n" + }, + { + "name": "o04-24", + "status": "ok", + "printed": " Odd.__mro__ ['Odd', 'P', 'Q', 'object']\n Odd().tag() P\n P comes before Q, which is backwards, and the lookup follows it anyway\n" + } + ] + }, { "slug": "t01-one-line-seven-stages", "cells": [ @@ -1198,7 +1257,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", @@ -1475,7 +1534,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", @@ -1509,12 +1568,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 0x1652458, 3 reference(s), 26 bytes, not tracked\nlist at 0x154e8b8, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x177da80, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x13f69a8, 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 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" }, { "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) 0x14fc2e0\nid(b) 0x178d2d0\nid(c) 0x14fc2e0\n" + "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" }, { "name": "t08-17", @@ -1554,7 +1613,7 @@ { "name": "t08-35", "status": "ok", - "printed": "dict at 0x12c16e8\ndict at 0x13ce7b0\nlist at 0x178d1b8\n" + "printed": "dict at 0x13edeb8\ndict at 0x14fd060\nlist at 0x1593030\n" }, { "name": "t08-37", @@ -1613,7 +1672,7 @@ { "name": "t09-21", "status": "ok", - "printed": "Node at 0x149cb98 -> Node at 0x13951e8 -> Node at 0x181f098 -> Node at 0x149cb98\n" + "printed": "Node at 0x1852210 -> Node at 0x168ecd8 -> Node at 0x18c7da8 -> Node at 0x1852210\n" }, { "name": "t09-23", @@ -1643,7 +1702,7 @@ { "name": "t09-37", "status": "ok", - "printed": "first object was at 0x178e3a8\nsecond object is at 0x178e3a8\nsame address reused -> True\n" + "printed": "first object was at 0x1131520\nsecond object is at 0x1131520\nsame address reused -> True\n" } ] }, diff --git a/probes/pyodide/lessons.md b/probes/pyodide/lessons.md index 122b450..4b82a27 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. -31 lesson(s) on Pyodide 3.14.2: 31 ran end to end, 328 cell(s) in total. +32 lesson(s) on Pyodide 3.14.2: 32 ran end to end, 339 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. @@ -27,6 +27,7 @@ The checks in `report.md` next to this ask whether a surface exists. This runs t | o01-the-header-byte-by-byte | 8 | runs end to end | | 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 | | 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 6218030..867b507 100644 --- a/pyxray/src/pyxray/glossary.py +++ b/pyxray/src/pyxray/glossary.py @@ -763,6 +763,24 @@ def anchor(name: str) -> str: see=("slot", "type object"), met="O03", ), + Term( + name="MRO", + short="The flat list of classes, in order, that a name is looked up in.", + long="Every type carries one as `tp_mro`, computed once when the class is made and recomputed for the whole subtree if `__bases__` is later assigned. It always starts with the type itself and ends with `object`. Attribute lookup, `super`, and the slot table all read this list rather than walking `__bases__`, which is why multiple inheritance has one answer instead of a search.", + cite="Objects/typeobject.c:3431-3451@v3.15.0rc1#mro_implementation_unlocked", + also=("method resolution order", "`__mro__`", "`tp_mro`"), + see=("C3 linearization", "type object"), + met="O04", + ), + Term( + name="C3 linearization", + short="The merge rule that turns a class and its bases into one ordered list.", + long="Take the MRO of each base, add the declared bases tuple, and repeatedly take the first head that does not appear later in any of the lists. If no such head exists the merge fails and you get a TypeError instead of a class. CPython spells this out in `pmerge`, and it is about forty lines. The rule guarantees a class comes before its bases and that the order you declared bases in is preserved.", + cite="Objects/typeobject.c:3361-3400@v3.15.0rc1#pmerge", + also=("C3", "the merge"), + see=("MRO",), + met="O04", + ), Term( name="static type", short="A type object written out as a C literal and compiled into the binary.",