From 753e10c275c77dc1ee376d424054a06648f67064 Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:31:28 +0700 Subject: [PATCH] F10: inside a code object What compiling a file actually hands you. The lesson walks a module down to a method through co_consts, because a nested definition is stored as a constant next to the numbers and strings, and then takes the record apart. The part worth the lesson is localsplus. co_varnames, co_cellvars and co_freevars look like three separate tuples and are three views of one array, filtered by tag bytes, and a parameter a nested function reads is tagged both local and cell so it appears in two of them. Seven slots come out of _varname_from_oparg and the tuples add up to eight. That is also why a closure's first instruction runs before line one: MAKE_CELL has to put the cell in the slot before anything can read it. Then co_flags one bit at a time, then equality, which compares the bytecode in its unspecialized form and never compares the filename. Two compiles of the same source from two different files are equal and hash the same, and a function run five thousand times still equals a fresh compile while _co_code_adaptive underneath it does not. Then replace and the AttributeError you get for trying to assign. Eighteen cells, six diagrams, six claims each with a cell under it, eight citations into Include/cpython/code.h, Objects/codeobject.c and Python/assemble.c. --- README.md | 1 + citations.lock.json | 35 + lessons/CLAIMS.md | 13 +- lessons/README.md | 1 + lessons/f10-inside-a-code-object/build.py | 362 +++++ lessons/f10-inside-a-code-object/diagrams.py | 129 ++ .../a-parameter-becomes-a-cell.excalidraw | 580 ++++++++ .../diagrams/a-parameter-becomes-a-cell.svg | 1 + .../diagrams/boxes-inside-boxes.excalidraw | 588 ++++++++ .../diagrams/boxes-inside-boxes.svg | 1 + .../diagrams/no-values-in-here.excalidraw | 473 +++++++ .../diagrams/no-values-in-here.svg | 1 + .../diagrams/one-array-four-views.excalidraw | 1220 +++++++++++++++++ .../diagrams/one-array-four-views.svg | 1 + .../diagrams/some-of-the-flags.excalidraw | 1035 ++++++++++++++ .../diagrams/some-of-the-flags.svg | 1 + .../what-equality-looks-at.excalidraw | 753 ++++++++++ .../diagrams/what-equality-looks-at.svg | 1 + lessons/f10-inside-a-code-object/f10.ipynb | 424 ++++++ probes/pyodide/lessons.json | 60 +- probes/pyodide/lessons.md | 3 +- 21 files changed, 5673 insertions(+), 10 deletions(-) create mode 100644 lessons/f10-inside-a-code-object/build.py create mode 100644 lessons/f10-inside-a-code-object/diagrams.py create mode 100644 lessons/f10-inside-a-code-object/diagrams/a-parameter-becomes-a-cell.excalidraw create mode 100644 lessons/f10-inside-a-code-object/diagrams/a-parameter-becomes-a-cell.svg create mode 100644 lessons/f10-inside-a-code-object/diagrams/boxes-inside-boxes.excalidraw create mode 100644 lessons/f10-inside-a-code-object/diagrams/boxes-inside-boxes.svg create mode 100644 lessons/f10-inside-a-code-object/diagrams/no-values-in-here.excalidraw create mode 100644 lessons/f10-inside-a-code-object/diagrams/no-values-in-here.svg create mode 100644 lessons/f10-inside-a-code-object/diagrams/one-array-four-views.excalidraw create mode 100644 lessons/f10-inside-a-code-object/diagrams/one-array-four-views.svg create mode 100644 lessons/f10-inside-a-code-object/diagrams/some-of-the-flags.excalidraw create mode 100644 lessons/f10-inside-a-code-object/diagrams/some-of-the-flags.svg create mode 100644 lessons/f10-inside-a-code-object/diagrams/what-equality-looks-at.excalidraw create mode 100644 lessons/f10-inside-a-code-object/diagrams/what-equality-looks-at.svg create mode 100644 lessons/f10-inside-a-code-object/f10.ipynb diff --git a/README.md b/README.md index e0954b3..5c7e295 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ A lesson can also end with a boss fight, which is a problem the text does not so | F07 | [The list becomes a graph](lessons/f07-the-list-becomes-a-graph/f07.ipynb) | The three rules that split bytecode into basic blocks, why the disassembly of a try is not in source order, unreachable code disappearing because nothing points at its block, why the stack depth of a handler cannot be added up in order, and how little of the graph survives into the code object | M3 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f07-the-list-becomes-a-graph/f07.ipynb) | | F08 | [The optimizer](lessons/f08-the-optimizer/f08.ipynb) | Why 2 ** 64 is worked out at compile time and 2 ** 65 is not, the four size limits written down as constants, the leftover number that survives because slot zero might be a docstring, a jump deleted by copying the two instructions it pointed at, the line break that costs you a superinstruction, and how the compiler proves a local has a value | M3 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f08-the-optimizer/f08.ipynb) | | F09 | [Two bytes at a time](lessons/f09-two-bytes-at-a-time/f09.ipynb) | Every byte of a two line function read by hand, the cache slots that are in the instruction stream but never run, why dis offsets go 0, 4, 6, 8, 20, how to rebuild a jump target from its argument, the boundary where one argument byte stops being enough, and the awful hack that sorts it out | M3 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f09-two-bytes-at-a-time/f09.ipynb) | +| F10 | [Inside a code object](lessons/f10-inside-a-code-object/f10.ipynb) | The record the compiler hands back, walked from a module down to a method, the seven slots behind three tuples that add up to eight, why a closure's first instruction runs before line one, every bit in co_flags, what equality compares and what it deliberately ignores, and what happens when you try to change one | M3 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f10-inside-a-code-object/f10.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 ab16780..b09e66f 100644 --- a/citations.lock.json +++ b/citations.lock.json @@ -65,11 +65,21 @@ "first_line": "fstring_replacement_field[expr_ty]:", "lines": 3 }, + "Include/cpython/code.h:118-153@v3.15.0rc1": { + "digest": "796288f2efca9a8f", + "first_line": "#define CO_OPTIMIZED 0x0001", + "lines": 36 + }, "Include/cpython/code.h:45-84@v3.15.0rc1": { "digest": "b8093c5db4f33759", "first_line": "#define _PyCode_DEF(SIZE) { \\", "lines": 40 }, + "Include/cpython/code.h:82-95@v3.15.0rc1": { + "digest": "6e0e3361dc8d74dd", + "first_line": "/* redundant values (derived from co_localsplusnames and \\", + "lines": 14 + }, "Include/cpython/listobject.h:5-22@v3.15.0rc1": { "digest": "707b180de55e923a", "first_line": "typedef struct {", @@ -115,6 +125,11 @@ "first_line": "struct _stmt {", "lines": 12 }, + "Include/internal/pycore_code.h:192-199@v3.15.0rc1": { + "digest": "fc05fedf627a517c", + "first_line": "#define CO_FAST_ARG_POS (0x02) // pos-only, pos-or-kw, varargs", + "lines": 8 + }, "Include/internal/pycore_flowgraph.h:27-31@v3.15.0rc1": { "digest": "5f318ca1d3438caa", "first_line": "struct _PyCfgBuilder* _PyCfg_FromInstructionSequence(_PyInstructionSequence *seq);", @@ -475,6 +490,21 @@ "first_line": "static int", "lines": 22 }, + "Objects/codeobject.c:2502-2541@v3.15.0rc1": { + "digest": "83840c731b19be0c", + "first_line": "code_richcompare(PyObject *self, PyObject *other, int op)", + "lines": 40 + }, + "Objects/codeobject.c:423-443@v3.15.0rc1": { + "digest": "9429e466c83d6c29", + "first_line": "static PyObject *", + "lines": 21 + }, + "Objects/codeobject.c:446-485@v3.15.0rc1": { + "digest": "ecced15598ef0f01", + "first_line": "_PyCode_Validate(struct _PyCodeConstructor *con)", + "lines": 40 + }, "Objects/codeobject.c:510-519@v3.15.0rc1": { "digest": "da2de0ce00cbfa54", "first_line": "static int", @@ -1320,6 +1350,11 @@ "first_line": "static int", "lines": 26 }, + "Python/assemble.c:516-528@v3.15.0rc1": { + "digest": "5891a4c578341344", + "first_line": "_PyLocals_Kind kind = CO_FAST_LOCAL | argvarkinds[i].kind;", + "lines": 13 + }, "Python/assemble.c:705-708@v3.15.0rc1": { "digest": "75168c43621dfa26", "first_line": "/* jump offsets are computed relative to", diff --git a/lessons/CLAIMS.md b/lessons/CLAIMS.md index 8709ccb..f5c1222 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. -223 claims across 25 lessons, 19 of them not observable from Python. +229 claims across 26 lessons, 19 of them not observable from Python. ## B01. Building CPython, and whether you need to @@ -148,6 +148,17 @@ Without it the exception becomes the rule and this goes back to being a book. | the step that first needs an EXTENDED_ARG is one pair bigger than every other step | [`f09-16`](f09-two-bytes-at-a-time/f09.ipynb) | | the same jump is a label before the assembler and a signed distance after it | [`f09-19`](f09-two-bytes-at-a-time/f09.ipynb) | +## F10. Inside a code object + +| Claim | Proved by | +| --- | --- | +| the code objects of a file form a tree, reachable through co_consts alone | [`f10-07`](f10-inside-a-code-object/f10.ipynb) | +| a parameter that a nested function reads is tagged twice, so it appears in two tuples | [`f10-09`](f10-inside-a-code-object/f10.ipynb) | +| a function whose parameter is captured starts with instructions that belong to no line of source | [`f10-11`](f10-inside-a-code-object/f10.ipynb) | +| a module body and a class body have no flags set at all, and a function has several | [`f10-13`](f10-inside-a-code-object/f10.ipynb) | +| the same source compiled from two different filenames gives two equal code objects | [`f10-15`](f10-inside-a-code-object/f10.ipynb) | +| replace gives you a new code object and leaves the original alone | [`f10-17`](f10-inside-a-code-object/f10.ipynb) | + ## T01. One line, seven stages | Claim | Proved by | diff --git a/lessons/README.md b/lessons/README.md index b1eddaa..e18236c 100644 --- a/lessons/README.md +++ b/lessons/README.md @@ -29,6 +29,7 @@ Each lesson is a notebook you can run. There is nothing to install and nothing t | [F07. The list becomes a graph](f07-the-list-becomes-a-graph/f07.ipynb) | The fourth compiler pass and the shape it works in, three rules for finding basic blocks, why a try compiles out of source order, unreachable code going because nothing points at its block, stack depth as a walk over every path, and cold handlers pushed past the end | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f07-the-list-becomes-a-graph/f07.ipynb) | | [F08. The optimizer](f08-the-optimizer/f08.ipynb) | The pass that runs while the graph is in that shape, constant folding and the four numbers that stop it, the unused constant that slot zero protects, jumps removed by copying a short ending, two instructions merged into one when they fit, and the graph walk behind LOAD_FAST_CHECK | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f08-the-optimizer/f08.ipynb) | | [F09. Two bytes at a time](f09-two-bytes-at-a-time/f09.ipynb) | The assembler, reading the bytes of a small function one pair at a time, the cache slots that make the offsets skip, why a jump argument is counted from after the fetch, what an EXTENDED_ARG costs, and the loop that has to run twice because widening a jump moves its target | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f09-two-bytes-at-a-time/f09.ipynb) | +| [F10. Inside a code object](f10-inside-a-code-object/f10.ipynb) | What compiling a file actually hands you, the code objects that live in other code objects' constants, the one array behind co_varnames and co_cellvars and co_freevars, the instructions that run before your first line, what co_flags remembers, and why two code objects can be equal without being the same one | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f10-inside-a-code-object/f10.ipynb) | ## The three programs diff --git a/lessons/f10-inside-a-code-object/build.py b/lessons/f10-inside-a-code-object/build.py new file mode 100644 index 0000000..0936093 --- /dev/null +++ b/lessons/f10-inside-a-code-object/build.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python +"""F10. Inside a code object. + +The tenth lesson of the front end part, and the twenty fourth overall. F09 assembled the bytes. +This one opens the box they came out in. + +The angle is that a code object is a plain record with nothing surprising in it, except for one +thing that surprises everybody: four of its attributes are not stored. `co_varnames`, +`co_cellvars` and `co_freevars` are computed on demand from one array of names and one string +of tag bytes, and because a tag byte can have two bits set, a name can appear in two of them. +That is why the tuples add up to more entries than there are slots. + +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("f10-inside-a-code-object", "f10") +badge = lesson.badge +cite = lesson.cite +term = lesson.term +figure = Diagrams("f10-inside-a-code-object").figure + + +lesson.md(f""" +# F10. Inside a code object + +{badge} + +Nine lessons to get here, and what comes out the end is one object you can hold in a variable. + +A {term("code object")} is the compiled form of exactly one thing: one module, one function body, one class body. It is a plain record. There is no cleverness in it and nothing lazy about it, and the single most useful thing to know is what is missing: it holds no values. Not the arguments, not the locals, not the result. A code object for a function that has been called a million times is the same object it was before the first call. + +Most of it reads exactly how you would expect. One part does not, and it is the part everybody trips over, so most of this lesson is about that. + +{figure("no-values-in-here", "the fields of a code object, none of which is a value the program computed")} +""") + + +lesson.md(""" +## About the source references + +Now and then this lesson points at CPython's own source, like this: `Include/cpython/code.h:82-95@v3.15.0rc1`. + +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. Where the two disagree, the lesson says so. +""") + + +lesson.code( + """ +import pyxray + +pyxray.show() +""", + differs=BANNER, + quiet=True, +) + + +lesson.md(f""" +## Boxes inside boxes + +Compile a file and you get one code object back. Every `def` and every `class` in that file produced a code object too, and each of those went into the enclosing one's `co_consts`, in with the numbers and the strings. + +So a module is a tree, and you can walk it with four lines of Python. There is no separate list of functions to consult and no registry anywhere. A nested definition is a constant. + +{lesson.claim("the code objects of a file form a tree, reachable through co_consts alone")} +""") + + +lesson.code(""" +import types + +SOURCE = \"\"\"\\\"\\\"\\\"A tiny module.\\\"\\\"\\\" + +import math + + +class Shape: + def area(self): + return math.pi + + +def counter(n): + while n: + yield n + n = n - 1 +\"\"\" + +module = compile(SOURCE, "tiny.py", "exec") + + +def walk(code, depth=0): + \"\"\"Every code object reachable from this one, through the constants.\"\"\" + print(f" {' ' * depth}{code.co_name:10} qualname {code.co_qualname}") + for one in code.co_consts: + if isinstance(one, types.CodeType): + walk(one, depth + 1) + + +walk(module) +""") + + +lesson.md(f""" +Four code objects out of one file. `co_name` is the short one and `co_qualname` is the path to it, which is how a traceback can say `Shape.area` when two classes both have a method called `area`. + +{figure("boxes-inside-boxes", "the code objects of one small file, nested through the constants")} + +Notice what is not in the tree. There is no code object for the class body's `def` line, because a `def` statement is just instructions in the body that made it. And in 3.12 and later a list comprehension is compiled into the function around it rather than into its own object, so writing one adds nothing here. + +## Names, in four tuples + +Now the confusing part. + +A code object has `co_varnames` for locals, `co_cellvars` for locals that a nested function reads, `co_freevars` for names it reads from an enclosing function, and `co_names` for everything looked up by name at run time: globals, attributes, imports. + +The first three of those are not stored. There is one array, `co_localsplusnames`, holding every slot a {term("frame")} will need, and one string of bytes next to it with one tag per slot. The header says so out loud, {cite("Include/cpython/code.h:82-95@v3.15.0rc1")}, where the four counts are under a comment reading "redundant values (derived from co_localsplusnames and co_localspluskinds)". + +Getting `co_varnames` runs {cite("Objects/codeobject.c:423-443@v3.15.0rc1#get_localsplus_names")}, which walks the array and keeps the slots whose tag matches. The test is `(k & kind) == 0`, a bitwise and, and the tags are single bits, {cite("Include/internal/pycore_code.h:192-199@v3.15.0rc1")}. A slot with two bits set comes back from two different calls. + +{lesson.claim("a parameter that a nested function reads is tagged twice, so it appears in two tuples")} +""") + + +lesson.code(""" +def outer(a, b=2, *args, c, **kw): + n = 0 + + def inner(): + return a + n + + return inner + + +code = outer.__code__ + +slots = [] +while True: + try: + slots.append(code._varname_from_oparg(len(slots))) + except IndexError: + break + +print(f" {len(slots)} slots in the array") +print(f" co_varnames has {len(code.co_varnames)}", end="") +print(f", co_cellvars has {len(code.co_cellvars)}", end="") +print(f", co_freevars has {len(code.co_freevars)}") +print() +for i, name in enumerate(slots): + tags = [ + label + for label, where in ( + ("local", code.co_varnames), + ("cell", code.co_cellvars), + ("free", code.co_freevars), + ) + if name in where + ] + print(f" slot {i} {name:8} {', '.join(tags)}") +""") + + +lesson.md(f""" +Seven slots. Six locals and two cells, which is eight, because `a` is both. + +{figure("one-array-four-views", "one array of slots, and the two tuples that overlap on one of them")} + +The reason is a single `|=` in the compiler, {cite("Python/assemble.c:516-528@v3.15.0rc1")}: every parameter gets `CO_FAST_LOCAL`, and then if the name is also in the cell variables, `CO_FAST_CELL` goes on top. It has to be a local, because that is where the caller puts the argument. It has to be a cell, because the nested function needs to see later changes to it rather than a copy. + +If you have ever added `len(co_varnames)` to `len(co_cellvars)` and got a number one too big, that is why. + +## The instructions before line one + +There is a visible consequence. A slot cannot be a plain value and a cell at the same time, so something has to convert it, and that something is an instruction at the very top of the function. + +{lesson.claim("a function whose parameter is captured starts with instructions that belong to no line of source")} +""") + + +lesson.code(""" +import dis + +dis.dis(outer) +""") + + +lesson.md(f""" +The first two instructions have `--` where a line number should be. `MAKE_CELL 0` takes the value the caller put in slot 0 and wraps it in a cell, in place. `MAKE_CELL 6` makes an empty cell for `n`, which has no value yet. + +{figure("a-parameter-becomes-a-cell", "a parameter arriving as a value and being turned into a cell before line one")} + +Then look at how the closure gets built. `LOAD_FAST_BORROW 0` and `LOAD_FAST_BORROW 6` load the two cell objects, `BUILD_TUPLE` puts them together, and `SET_FUNCTION_ATTRIBUTE 8` hangs that tuple on the new function. Inside `inner`, `COPY_FREE_VARS 2` pulls them out again. Every one of those is an ordinary instruction. There is no hidden machinery. + +## co_flags is one integer + +`co_flags` is a bit field decided at compile time. Nothing sets a bit in it later. + +The definitions are {cite("Include/cpython/code.h:118-153@v3.15.0rc1")}, and `dis` will give you their names. + +{lesson.claim("a module body and a class body have no flags set at all, and a function has several")} +""") + + +lesson.code(""" +import dis + + +def find(code, name): + \"\"\"The first code object with this name, anywhere inside this one.\"\"\" + for one in code.co_consts: + if isinstance(one, types.CodeType): + if one.co_name == name: + return one + deeper = find(one, name) + if deeper is not None: + return deeper + return None + + +for name in ("", "Shape", "area", "counter"): + found = module if name == "" else find(module, name) + bits = [text for bit, text in sorted(dis.COMPILER_FLAG_NAMES.items()) if found.co_flags & bit] + print(f" {name:10} {found.co_flags:>10} {', '.join(bits) or 'nothing set'}") +""") + + +lesson.md(f""" +`CO_OPTIMIZED` is the one that matters most. It means the locals of this code object live in numbered slots rather than in a dictionary, which is why `LOAD_FAST` exists and why you cannot add a local to a running function. A module body does not have it. That is the whole reason module level code is slower than the same code in a function. + +{figure("some-of-the-flags", "four of the co_flags bits and what each one changes")} + +`CO_GENERATOR` on `counter` is set because the body contains a `yield`. The compiler decided that while walking the tree, four lessons ago, and calling `counter` returns a generator instead of running the body because of that one bit. + +## Two of them can be equal + +Code objects compare by value, and the list of what gets compared is worth reading, because of what is missing from it: {cite("Objects/codeobject.c:2502-2541@v3.15.0rc1#code_richcompare")} checks the name, the argument counts, the flags, the first line number, the bytecode, the constants, the names and both side tables. It never looks at `co_filename`. + +The bytecode comparison is the other interesting bit. It reads through `_Py_GetBaseCodeUnit`, which undoes {term("specialization")}, so a function that has been running hot for an hour still compares equal to a freshly compiled copy of itself. + +{lesson.claim("the same source compiled from two different filenames gives two equal code objects")} +""") + + +lesson.code(""" +SOURCE = "def h(x):\\n return x * 2\\n" + +here = compile(SOURCE, "/one/place.py", "exec").co_consts[0] +there = compile(SOURCE, "/somewhere/else.py", "exec").co_consts[0] + +print(f" filenames {here.co_filename} and {there.co_filename}") +print(f" equal {here == there}") +print(f" same hash {hash(here) == hash(there)}") +print(f" same object {here is there}") +print() + +namespace = {} +exec(compile(SOURCE, "/one/place.py", "exec"), namespace) +for _ in range(5000): + namespace["h"](3) + +warm = namespace["h"].__code__ +print(f" after 5000 calls, still equal to a fresh compile {warm == there}") +print(f" co_code is byte for byte identical {warm.co_code == there.co_code}") +adaptive_differs = warm._co_code_adaptive != there._co_code_adaptive +print(f" the adaptive copy underneath it is not {adaptive_differs}") +""") + + +lesson.md(f""" +The filename is carried so a traceback can tell you where to look. It is not part of what the object is. + +{figure("what-equality-looks-at", "the fields equality reads and the ones it ignores")} + +The last two lines are a preview of F14. `co_code` gives you the instructions the compiler produced. `_co_code_adaptive` gives you the ones the interpreter is actually running, which change as it learns. Equality uses the first, which is the only sensible choice, since otherwise a function would stop being equal to itself partway through a loop. + +## You cannot change one + +Code objects are immutable. There is no setter for any of the `co_` attributes. What there is instead is `replace`, which builds a new one with some fields swapped, and it is how tools like `coverage` and `cloudpickle` get their work done. + +{lesson.claim("replace gives you a new code object and leaves the original alone")} +""") + + +lesson.code(""" +renamed = here.replace(co_name="something_else", co_filename="/made/up.py") + +print(f" original {here.co_name:16} {here.co_filename}") +print(f" new {renamed.co_name:16} {renamed.co_filename}") +print(f" equal to the original now {renamed == here}") +print() +try: + here.co_name = "nope" +except AttributeError as problem: + print(f" assigning to co_name says: {problem}") +""") + + +lesson.md(f""" +Changing the name broke equality, because the name is compared. Changing the filename would not have. + +Everything a new code object goes through on the way in is {cite("Objects/codeobject.c:446-485@v3.15.0rc1#_PyCode_Validate")}, which checks the argument counts against each other, checks the bytecode is an even number of bytes, and refuses anything that does not add up. That is the guard between a valid code object and a crash, and it is worth knowing it exists before you go building one by hand. + +## Try it yourself + +1. Walk the code objects of a real module from the standard library. Which one has the most constants? +2. Find a function where `co_names` and `co_varnames` share a name. What has to be true for that? +3. Compile a lambda. What is its `co_name`, and what is its `co_qualname`? +4. Use `replace` to change `co_consts` of a simple function and build a working function from the result with `types.FunctionType`. +5. Set `CO_OPTIMIZED` on a module body with `replace` and try to run it. What happens, and why is that fair? + +## What just happened + +A code object is the compiled form of one module, function body or class body, and it holds no values at all. That is what lets one of them serve every call. + +Compiling a file gives you one code object with the rest nested inside it, through `co_consts`. There is no other index. A nested `def` is a constant. + +Names are the confusing part. `co_varnames`, `co_cellvars` and `co_freevars` are not stored. They are filtered out of one array of slots using one tag byte per slot, and a tag byte can have more than one bit set, so a parameter that a nested function reads appears in two of them and gets counted twice. + +That double tagging is visible from the outside as `MAKE_CELL` instructions at the top of a function, which have no line number because they belong to no line you wrote. + +`co_flags` is a bit field fixed at compile time. `CO_OPTIMIZED` is the one that decides whether locals are slots or dictionary entries, and a module body does not have it. + +Two code objects compiled from different files can be equal and hash the same, because the filename is not part of the comparison. Neither is anything the specializer wrote after compilation. + +## What is next + +F11 is the two side tables this lesson skipped: the {term("line table")} and the {term("exception table")}. Both are compressed byte formats, both are decodable by hand, and one of them explains why a `try` block costs nothing until something goes wrong. +""") + + +raise SystemExit(lesson.save()) diff --git a/lessons/f10-inside-a-code-object/diagrams.py b/lessons/f10-inside-a-code-object/diagrams.py new file mode 100644 index 0000000..96a0ac1 --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +"""The diagrams for F10, inside a code object. + +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 `one-array-four-views`. Everything else follows from a code +object being a plain record with no values in it, whose most confusing part is that four of its +attributes are not stored at all but computed from one array and one string of tag bytes. +""" + +from nbdiagram import Gallery, figures + +gallery = Gallery("f10-inside-a-code-object") + +gallery.add( + figures.table( + "one-array-four-views", + ["slot", "name", "tagged local", "tagged cell"], + [ + ["0", "a", "yes", "yes"], + ["1", "b", "yes", "no"], + ["5", "inner", "yes", "no"], + ["6", "n", "no", "yes"], + ], + title="Seven slots in one array, and co_varnames plus co_cellvars adds up to eight", + caption="A parameter that a nested function reads is tagged twice, so it turns up in two tuples and is counted twice.", + tones=["focus", "quiet", "quiet", "focus"], + ) +) + + +gallery.add( + figures.tree( + "boxes-inside-boxes", + ( + "the module", + [ + ("the class body", ["a method"]), + ("a function", ["the listcomp is inlined, so no box"]), + ], + ), + title="Compiling one file gives you one code object, with the rest inside it", + caption="A nested definition ends up in the outer object's constants, next to the numbers and strings.", + ) +) + + +gallery.add( + figures.flow( + "a-parameter-becomes-a-cell", + [ + "a arrives in slot 0, an ordinary parameter", + "MAKE_CELL 0 runs before anything else", + "slot 0 now holds a cell with a inside it", + "the nested function gets the cell, not the value", + ], + title="Why the first two instructions of a function can run before line one", + labels=[ + "tagged both local and cell", + "which is what makes the closure work", + ], + tones=["input", "focus", "intermediate", "durable"], + ) +) + + +gallery.add( + figures.compare( + "what-equality-looks-at", + ( + "compared", + [ + "the name and the argument counts", + "the flags and the first line number", + "the bytecode, in its unspecialized form", + "the constants, names and side tables", + ], + ), + ( + "not compared", + [ + "the file it was compiled from", + "anything the specializer wrote later", + "the stack size", + "how many times it has run", + ], + ), + title="The same source compiled twice, from two different files", + verdict="They are equal, and they hash the same. The filename is carried for tracebacks, not identity.", + verdict_tone="focus", + ) +) + + +gallery.add( + figures.stack( + "no-values-in-here", + [ + "co_code, the instructions", + "co_consts, the literals and the nested code objects", + "co_names, everything looked up by name at run time", + "co_localsplusnames, the slots a frame will need", + "co_flags, co_stacksize, co_firstlineno", + ], + title="Everything a code object holds, and none of it is a value your program computed", + note="One code object serves every call. The values live in a frame, which is what F13 builds.", + ) +) + + +gallery.add( + figures.table( + "some-of-the-flags", + ["bit", "name", "what it tells the interpreter"], + [ + ["0x0001", "CO_OPTIMIZED", "locals are array slots, not a dictionary"], + ["0x0004", "CO_VARARGS", "there is a *args parameter"], + ["0x0020", "CO_GENERATOR", "calling this returns a generator"], + ["0x8000000", "CO_METHOD", "this was defined inside a class body"], + ], + title="co_flags is one integer, and every bit in it was decided at compile time", + caption="A module body and a class body have no flags set at all. Only functions get the interesting ones.", + tones=["focus", "quiet", "quiet", "focus"], + ) +) + + +raise SystemExit(gallery.save()) diff --git a/lessons/f10-inside-a-code-object/diagrams/a-parameter-becomes-a-cell.excalidraw b/lessons/f10-inside-a-code-object/diagrams/a-parameter-becomes-a-cell.excalidraw new file mode 100644 index 0000000..ef740b8 --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/a-parameter-becomes-a-cell.excalidraw @@ -0,0 +1,580 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "808363b4462511455fd9", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 888.4799999999999, + "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 the first two instructions of a function can run before line one", + "originalText": "Why the first two instructions of a function can run before line one", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "c02119e248a1e1ad3e6e", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 549.5, + "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": "0a657aa2f1e3bc527d5a", + "type": "text" + }, + { + "id": "3fa8eff4ee2ab1fcaad9", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "0a657aa2f1e3bc527d5a", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 517.5, + "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 arrives in slot 0, an ordinary parameter", + "originalText": "a arrives in slot 0, an ordinary parameter", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c02119e248a1e1ad3e6e", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "ef467fe24e550bcfb6b2", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 549.5, + "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": "2f7a463eb493f8143824", + "type": "text" + }, + { + "id": "3fa8eff4ee2ab1fcaad9", + "type": "arrow" + }, + { + "id": "61b18dbb0fa2d56254bd", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "2f7a463eb493f8143824", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 517.5, + "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": "MAKE_CELL 0 runs before anything else", + "originalText": "MAKE_CELL 0 runs before anything else", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ef467fe24e550bcfb6b2", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "8a2d7b81d8d67f28ccc1", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 549.5, + "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": "ddc80c398d2f4011b63d", + "type": "text" + }, + { + "id": "61b18dbb0fa2d56254bd", + "type": "arrow" + }, + { + "id": "d798301ee94f30e29a9e", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "ddc80c398d2f4011b63d", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 517.5, + "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": "slot 0 now holds a cell with a inside it", + "originalText": "slot 0 now holds a cell with a inside it", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "8a2d7b81d8d67f28ccc1", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "4aa30c166f695925258e", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 549.5, + "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": "480fbaac1a0e4e844ff0", + "type": "text" + }, + { + "id": "d798301ee94f30e29a9e", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "480fbaac1a0e4e844ff0", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 517.5, + "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 nested function gets the cell, not the value", + "originalText": "the nested function gets the cell, not the value", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "4aa30c166f695925258e", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "3fa8eff4ee2ab1fcaad9", + "type": "arrow", + "x": 274.75, + "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": "c02119e248a1e1ad3e6e", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "ef467fe24e550bcfb6b2", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "851ba16b035911319f87", + "type": "text", + "x": 286.75, + "y": 140.0, + "width": 228.0, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "tagged both local and cell", + "originalText": "tagged both local and cell", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "61b18dbb0fa2d56254bd", + "type": "arrow", + "x": 274.75, + "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": "ef467fe24e550bcfb6b2", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "8a2d7b81d8d67f28ccc1", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "434f7a3c15476c567b86", + "type": "text", + "x": 286.75, + "y": 270.0, + "width": 343.44, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "which is what makes the closure work", + "originalText": "which is what makes the closure work", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d798301ee94f30e29a9e", + "type": "arrow", + "x": 274.75, + "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": "8a2d7b81d8d67f28ccc1", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "4aa30c166f695925258e", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/f10-inside-a-code-object/diagrams/a-parameter-becomes-a-cell.svg b/lessons/f10-inside-a-code-object/diagrams/a-parameter-becomes-a-cell.svg new file mode 100644 index 0000000..243a486 --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/a-parameter-becomes-a-cell.svg @@ -0,0 +1 @@ +Why the first two instructions of a function can run before line onea arrives in slot 0, an ordinary parameterMAKE_CELL 0 runs before anything elseslot 0 now holds a cell with a inside itthe nested function gets the cell, not the valuetagged both local and cellwhich is what makes the closure work diff --git a/lessons/f10-inside-a-code-object/diagrams/boxes-inside-boxes.excalidraw b/lessons/f10-inside-a-code-object/diagrams/boxes-inside-boxes.excalidraw new file mode 100644 index 0000000..99e7a51 --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/boxes-inside-boxes.excalidraw @@ -0,0 +1,588 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "42a9416dcd81affe6fd0", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 908.28, + "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": "Compiling one file gives you one code object, with the rest inside it", + "originalText": "Compiling one file gives you one code object, with the rest inside it", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "77cfb91758ca351df34f", + "type": "rectangle", + "x": 0.0, + "y": 242.0, + "width": 108.8, + "height": 52.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": "e0bfc0d8e86a3e22bd11", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "e0bfc0d8e86a3e22bd11", + "type": "text", + "x": 16.0, + "y": 258.0, + "width": 76.8, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "a method", + "originalText": "a method", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "77cfb91758ca351df34f", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "67accae66cd070337e22", + "type": "rectangle", + "x": -28.800000000000004, + "y": 146.0, + "width": 166.4, + "height": 52.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": "3bcaf095c1984434185e", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "3bcaf095c1984434185e", + "type": "text", + "x": -12.800000000000004, + "y": 162.0, + "width": 134.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "the class body", + "originalText": "the class body", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "67accae66cd070337e22", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "91d02c8658e9192bf6d4", + "type": "line", + "x": 54.39999999999999, + "y": 198.0, + "width": 7.105427357601002e-15, + "height": 44.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.0 + ], + [ + 7.105427357601002e-15, + 44.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "dd660e764e35bd2a8e3c", + "type": "rectangle", + "x": 186.4, + "y": 242.0, + "width": 358.4, + "height": 52.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": "d66947904ef58c8c845e", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "d66947904ef58c8c845e", + "type": "text", + "x": 202.4, + "y": 258.0, + "width": 326.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, + "text": "the listcomp is inlined, so no box", + "originalText": "the listcomp is inlined, so no box", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "dd660e764e35bd2a8e3c", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "e5031e7c9f22f21cd8c6", + "type": "rectangle", + "x": 301.59999999999997, + "y": 146.0, + "width": 128.0, + "height": 52.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": "e660b9bb9a8bdf75797e", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "e660b9bb9a8bdf75797e", + "type": "text", + "x": 317.59999999999997, + "y": 162.0, + "width": 96.0, + "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, + "text": "a function", + "originalText": "a function", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "e5031e7c9f22f21cd8c6", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "5349450da1841c46b171", + "type": "line", + "x": 365.59999999999997, + "y": 198.0, + "width": 0.0, + "height": 44.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.0 + ], + [ + 0.0, + 44.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "3dd6c1291cedef50d726", + "type": "rectangle", + "x": 145.99999999999997, + "y": 50.0, + "width": 128.0, + "height": 52.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": "7ed4c63e3c26aa450896", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "7ed4c63e3c26aa450896", + "type": "text", + "x": 161.99999999999997, + "y": 66.0, + "width": 96.0, + "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, + "text": "the module", + "originalText": "the module", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3dd6c1291cedef50d726", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "ebb500c54842bb416367", + "type": "line", + "x": 210.0, + "y": 102.0, + "width": 155.60000000000002, + "height": 44.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.0 + ], + [ + -155.60000000000002, + 44.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "ab928739e9ab1f03af80", + "type": "line", + "x": 210.0, + "y": 102.0, + "width": 155.59999999999997, + "height": 44.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.0 + ], + [ + 155.59999999999997, + 44.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "a0a32a717a0caf1c94fa", + "type": "text", + "x": 0.0, + "y": 314.0, + "width": 812.16, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "A nested definition ends up in the outer object's constants, next to the numbers and strings.", + "originalText": "A nested definition ends up in the outer object's constants, next to the numbers and strings.", + "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/f10-inside-a-code-object/diagrams/boxes-inside-boxes.svg b/lessons/f10-inside-a-code-object/diagrams/boxes-inside-boxes.svg new file mode 100644 index 0000000..68dc8b0 --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/boxes-inside-boxes.svg @@ -0,0 +1 @@ +Compiling one file gives you one code object, with the rest inside ita methodthe class bodythe listcomp is inlined, so no boxa functionthe moduleA nested definition ends up in the outer object's constants, next to the numbers and strings. diff --git a/lessons/f10-inside-a-code-object/diagrams/no-values-in-here.excalidraw b/lessons/f10-inside-a-code-object/diagrams/no-values-in-here.excalidraw new file mode 100644 index 0000000..cb5513d --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/no-values-in-here.excalidraw @@ -0,0 +1,473 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "7d795d6a77d40d0dabf1", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 1068.48, + "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": "Everything a code object holds, and none of it is a value your program computed", + "originalText": "Everything a code object holds, and none of it is a value your program computed", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "efec9b9ea49934761238", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 644.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "2499c7446f1be7984740", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "2499c7446f1be7984740", + "type": "text", + "x": 16.0, + "y": 66.0, + "width": 612.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "co_flags, co_stacksize, co_firstlineno", + "originalText": "co_flags, co_stacksize, co_firstlineno", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "efec9b9ea49934761238", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "a9085da251126bd8e0d7", + "type": "text", + "x": 660.0, + "y": 66.0, + "width": 25.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "top", + "originalText": "top", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "8411032680ccd329491f", + "type": "rectangle", + "x": 0.0, + "y": 102.0, + "width": 644.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "03b335cbd3e307834724", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "03b335cbd3e307834724", + "type": "text", + "x": 16.0, + "y": 118.0, + "width": 612.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "co_localsplusnames, the slots a frame will need", + "originalText": "co_localsplusnames, the slots a frame will need", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "8411032680ccd329491f", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "d7880c9fcc18538e1cee", + "type": "rectangle", + "x": 0.0, + "y": 154.0, + "width": 644.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "25718cc0b36a57011472", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "25718cc0b36a57011472", + "type": "text", + "x": 16.0, + "y": 170.0, + "width": 612.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "co_names, everything looked up by name at run time", + "originalText": "co_names, everything looked up by name at run time", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "d7880c9fcc18538e1cee", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "ed1dcc0ce6010913b6e2", + "type": "rectangle", + "x": 0.0, + "y": 206.0, + "width": 644.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "08c15965fdb635f53cdb", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "08c15965fdb635f53cdb", + "type": "text", + "x": 16.0, + "y": 222.0, + "width": 612.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "co_consts, the literals and the nested code objects", + "originalText": "co_consts, the literals and the nested code objects", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "ed1dcc0ce6010913b6e2", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "3c500aa83f9fb8b0cc28", + "type": "rectangle", + "x": 0.0, + "y": 258.0, + "width": 644.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "cca28f4fbeebcc28dfa2", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "cca28f4fbeebcc28dfa2", + "type": "text", + "x": 16.0, + "y": 274.0, + "width": 612.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "co_code, the instructions", + "originalText": "co_code, the instructions", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "3c500aa83f9fb8b0cc28", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "be0aeb4aaecd554c5f19", + "type": "text", + "x": 0.0, + "y": 330.0, + "width": 790.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": "One code object serves every call. The values live in a frame, which is what F13 builds.", + "originalText": "One code object serves every call. The values live in a frame, which is what F13 builds.", + "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/f10-inside-a-code-object/diagrams/no-values-in-here.svg b/lessons/f10-inside-a-code-object/diagrams/no-values-in-here.svg new file mode 100644 index 0000000..2cc60d4 --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/no-values-in-here.svg @@ -0,0 +1 @@ +Everything a code object holds, and none of it is a value your program computedco_flags, co_stacksize, co_firstlinenotopco_localsplusnames, the slots a frame will needco_names, everything looked up by name at run timeco_consts, the literals and the nested code objectsco_code, the instructionsOne code object serves every call. The values live in a frame, which is what F13 builds. diff --git a/lessons/f10-inside-a-code-object/diagrams/one-array-four-views.excalidraw b/lessons/f10-inside-a-code-object/diagrams/one-array-four-views.excalidraw new file mode 100644 index 0000000..1f7dde0 --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/one-array-four-views.excalidraw @@ -0,0 +1,1220 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "c82582006c7e72a7d143", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 1013.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": "Seven slots in one array, and co_varnames plus co_cellvars adds up to eight", + "originalText": "Seven slots in one array, and co_varnames plus co_cellvars adds up to eight", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "9f97dfcb9ae364de4e8a", + "type": "text", + "x": 14.0, + "y": 50.0, + "width": 31.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": "slot", + "originalText": "slot", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "68f29af78f8d6a1b8a7d", + "type": "text", + "x": 80.4, + "y": 50.0, + "width": 44.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": "name", + "originalText": "name", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e81f72e089a9d886c656", + "type": "text", + "x": 156.4, + "y": 50.0, + "width": 104.39999999999999, + "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": "tagged local", + "originalText": "tagged local", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2cb160574eb68cba0317", + "type": "text", + "x": 299.6, + "y": 50.0, + "width": 94.8, + "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": "tagged cell", + "originalText": "tagged cell", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "60c627b64f6e593d01e9", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 419.2, + "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 + ], + [ + 419.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "8a1e44d00a61a328eedf", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 419.2, + "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 + ], + [ + 419.2, + 0.0 + ], + [ + 419.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "3da222727c3bcce44c79", + "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": "0", + "originalText": "0", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a7e02739c4c36f00286a", + "type": "text", + "x": 80.4, + "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": "6e73973e914c5c3427e3", + "type": "text", + "x": 156.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": "9fadecd755dbeb61bf7f", + "type": "text", + "x": 299.6, + "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": "52528e0f73dc61179fc4", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 419.2, + "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 + ], + [ + 419.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "bbf1619615add6e46732", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 419.2, + "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 + ], + [ + 419.2, + 0.0 + ], + [ + 419.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "b692b2a7e0aef9f2daad", + "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": "1", + "originalText": "1", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "1d375b94ca515bb6f488", + "type": "text", + "x": 80.4, + "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": "34c744f4ff15e82d7928", + "type": "text", + "x": 156.4, + "y": 126.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": "103a1cb0f2d673e8326d", + "type": "text", + "x": 299.6, + "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": "f4ac0e9e3af8c6e2cb97", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 419.2, + "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 + ], + [ + 419.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "91ebb5bb0e70eb486fe6", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 419.2, + "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 + ], + [ + 419.2, + 0.0 + ], + [ + 419.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "681332cb4f5cffb79b2d", + "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": "5", + "originalText": "5", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "cb87e0c4cd9cdf4f0316", + "type": "text", + "x": 80.4, + "y": 166.0, + "width": 48.0, + "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": "inner", + "originalText": "inner", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "b81ef11193c6992cbfcf", + "type": "text", + "x": 156.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": "843ca3327cdb55d660cd", + "type": "text", + "x": 299.6, + "y": 166.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": "bfc4782bd276c794c326", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 419.2, + "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 + ], + [ + 419.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "80a5fb4f6f165b326c43", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 419.2, + "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 + ], + [ + 419.2, + 0.0 + ], + [ + 419.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "00728d72c82d05ce1643", + "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": "6", + "originalText": "6", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e3cc63cda67e5652904b", + "type": "text", + "x": 80.4, + "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": "n", + "originalText": "n", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "8014479de6c3b6ea8d05", + "type": "text", + "x": 156.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": "d21de6b8b2a8d46ca9f6", + "type": "text", + "x": 299.6, + "y": 206.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": "907ecff5f6ec47375c2a", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 419.2, + "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 + ], + [ + 419.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "469543fd79e54e5f8b1e", + "type": "text", + "x": 0.0, + "y": 256.0, + "width": 959.04, + "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 parameter that a nested function reads is tagged twice, so it turns up in two tuples and is counted twice.", + "originalText": "A parameter that a nested function reads is tagged twice, so it turns up in two tuples and is counted twice.", + "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/f10-inside-a-code-object/diagrams/one-array-four-views.svg b/lessons/f10-inside-a-code-object/diagrams/one-array-four-views.svg new file mode 100644 index 0000000..8e000bf --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/one-array-four-views.svg @@ -0,0 +1 @@ +Seven slots in one array, and co_varnames plus co_cellvars adds up to eightslotnametagged localtagged cell0ayesyes1byesno5inneryesno6nnoyesA parameter that a nested function reads is tagged twice, so it turns up in two tuples and is counted twice. diff --git a/lessons/f10-inside-a-code-object/diagrams/some-of-the-flags.excalidraw b/lessons/f10-inside-a-code-object/diagrams/some-of-the-flags.excalidraw new file mode 100644 index 0000000..8c7c06b --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/some-of-the-flags.excalidraw @@ -0,0 +1,1035 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "47dd6f9c0bd00ae99556", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 960.1200000000001, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "co_flags is one integer, and every bit in it was decided at compile time", + "originalText": "co_flags is one integer, and every bit in it was decided at compile time", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "b700d1cab6429c064158", + "type": "text", + "x": 14.0, + "y": 50.0, + "width": 21.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": "bit", + "originalText": "bit", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "9b30b4457e103f619f16", + "type": "text", + "x": 128.39999999999998, + "y": 50.0, + "width": 44.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": "name", + "originalText": "name", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "c20b23c5a4826bde25a4", + "type": "text", + "x": 271.59999999999997, + "y": 50.0, + "width": 237.36, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "what it tells the interpreter", + "originalText": "what it tells the interpreter", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2e3251d80e3223e6a459", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 669.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 669.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "a1cd96b8a068f9c46d13", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 669.6, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 669.6, + 0.0 + ], + [ + 669.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "1db3f0b80b313d0b24b5", + "type": "text", + "x": 14.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": "0x0001", + "originalText": "0x0001", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "86f07c2ad155fdaee099", + "type": "text", + "x": 128.39999999999998, + "y": 86.0, + "width": 115.19999999999999, + "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": "CO_OPTIMIZED", + "originalText": "CO_OPTIMIZED", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "5f79fd4af9569f38cc33", + "type": "text", + "x": 271.59999999999997, + "y": 86.0, + "width": 384.0, + "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": "locals are array slots, not a dictionary", + "originalText": "locals are array slots, not a dictionary", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "ff8d80909b04391e3616", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 669.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 669.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "050b3c8928812df17b90", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 669.6, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 669.6, + 0.0 + ], + [ + 669.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "ea2bacfa398eb3e91821", + "type": "text", + "x": 14.0, + "y": 126.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": "0x0004", + "originalText": "0x0004", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "233ed45ea6fc11382664", + "type": "text", + "x": 128.39999999999998, + "y": 126.0, + "width": 96.0, + "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": "CO_VARARGS", + "originalText": "CO_VARARGS", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "44d3068f14ec2f0dc845", + "type": "text", + "x": 271.59999999999997, + "y": 126.0, + "width": 249.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": "there is a *args parameter", + "originalText": "there is a *args parameter", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "38f0b94c8bbb531b9e8a", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 669.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 669.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "42e8bc4c2fda5424990a", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 669.6, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 669.6, + 0.0 + ], + [ + 669.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "2e9f32328ea09722968f", + "type": "text", + "x": 14.0, + "y": 166.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": "0x0020", + "originalText": "0x0020", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "3a4a98b2209fcc25c06e", + "type": "text", + "x": 128.39999999999998, + "y": 166.0, + "width": 115.19999999999999, + "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": "CO_GENERATOR", + "originalText": "CO_GENERATOR", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "174479b6d8ca2495bcc4", + "type": "text", + "x": 271.59999999999997, + "y": 166.0, + "width": 307.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "calling this returns a generator", + "originalText": "calling this returns a generator", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "3adc1e0caae4548e39ba", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 669.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 669.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "084b1bd120d4f99b8b8d", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 669.6, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 669.6, + 0.0 + ], + [ + 669.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "1fd2e79aab92246a4e40", + "type": "text", + "x": 14.0, + "y": 206.0, + "width": 86.39999999999999, + "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": "0x8000000", + "originalText": "0x8000000", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "598e9ac3b1668ea20a49", + "type": "text", + "x": 128.39999999999998, + "y": 206.0, + "width": 86.39999999999999, + "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": "CO_METHOD", + "originalText": "CO_METHOD", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "263b0ca34179e8664e2f", + "type": "text", + "x": 271.59999999999997, + "y": 206.0, + "width": 345.59999999999997, + "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": "this was defined inside a class body", + "originalText": "this was defined inside a class body", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "c3966c09c86376bd78df", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 669.6, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 669.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "5b647bee08bd3501fd74", + "type": "text", + "x": 0.0, + "y": 256.0, + "width": 861.36, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "A module body and a class body have no flags set at all. Only functions get the interesting ones.", + "originalText": "A module body and a class body have no flags set at all. Only functions get the interesting ones.", + "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/f10-inside-a-code-object/diagrams/some-of-the-flags.svg b/lessons/f10-inside-a-code-object/diagrams/some-of-the-flags.svg new file mode 100644 index 0000000..b604aaf --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/some-of-the-flags.svg @@ -0,0 +1 @@ +co_flags is one integer, and every bit in it was decided at compile timebitnamewhat it tells the interpreter0x0001CO_OPTIMIZEDlocals are array slots, not a dictionary0x0004CO_VARARGSthere is a *args parameter0x0020CO_GENERATORcalling this returns a generator0x8000000CO_METHODthis was defined inside a class bodyA module body and a class body have no flags set at all. Only functions get the interesting ones. diff --git a/lessons/f10-inside-a-code-object/diagrams/what-equality-looks-at.excalidraw b/lessons/f10-inside-a-code-object/diagrams/what-equality-looks-at.excalidraw new file mode 100644 index 0000000..0507865 --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/what-equality-looks-at.excalidraw @@ -0,0 +1,753 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "88af2d3a9ebec66761a7", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 757.8, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "The same source compiled twice, from two different files", + "originalText": "The same source compiled twice, from two different files", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "46e06960fb326236ba79", + "type": "text", + "x": 200.65, + "y": 50.0, + "width": 98.69999999999999, + "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": "compared", + "originalText": "compared", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "1c31848264075e4a9f2f", + "type": "rectangle", + "x": 0.0, + "y": 87.0, + "width": 500.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": "3cdf7ae105009e28c95e", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "3cdf7ae105009e28c95e", + "type": "text", + "x": 16.0, + "y": 103.0, + "width": 468.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 name and the argument counts", + "originalText": "the name and the argument counts", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "1c31848264075e4a9f2f", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "cd7dfe40d4e292443a37", + "type": "rectangle", + "x": 0.0, + "y": 147.0, + "width": 500.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": "5ce68c5210945ea3b2e2", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "5ce68c5210945ea3b2e2", + "type": "text", + "x": 16.0, + "y": 163.0, + "width": 468.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 flags and the first line number", + "originalText": "the flags and the first line number", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "cd7dfe40d4e292443a37", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c1922091a3630e652f70", + "type": "rectangle", + "x": 0.0, + "y": 207.0, + "width": 500.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": "33345e24d84cb4b3c7d3", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "33345e24d84cb4b3c7d3", + "type": "text", + "x": 16.0, + "y": 223.0, + "width": 468.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 bytecode, in its unspecialized form", + "originalText": "the bytecode, in its unspecialized form", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "c1922091a3630e652f70", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "aca7f0f1b097b13de8d6", + "type": "rectangle", + "x": 0.0, + "y": 267.0, + "width": 500.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": "45ae36125ba94716b51d", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "45ae36125ba94716b51d", + "type": "text", + "x": 16.0, + "y": 283.0, + "width": 468.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 constants, names and side tables", + "originalText": "the constants, names and side tables", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "aca7f0f1b097b13de8d6", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "976c5e64b1b94403eefe", + "type": "text", + "x": 738.9, + "y": 50.0, + "width": 142.2, + "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": "not compared", + "originalText": "not compared", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f1353008379811480636", + "type": "rectangle", + "x": 560.0, + "y": 87.0, + "width": 500.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": "2dde7da1c21759f35db3", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "2dde7da1c21759f35db3", + "type": "text", + "x": 576.0, + "y": 103.0, + "width": 468.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 file it was compiled from", + "originalText": "the file it was compiled from", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "f1353008379811480636", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "8cc692568123abfd697d", + "type": "rectangle", + "x": 560.0, + "y": 147.0, + "width": 500.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": "d318c276e092cbad5dc7", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "d318c276e092cbad5dc7", + "type": "text", + "x": 576.0, + "y": 163.0, + "width": 468.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": "anything the specializer wrote later", + "originalText": "anything the specializer wrote later", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "8cc692568123abfd697d", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "0e1435368efe4833b68a", + "type": "rectangle", + "x": 560.0, + "y": 207.0, + "width": 500.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": "8f20b85adb5a55489119", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "8f20b85adb5a55489119", + "type": "text", + "x": 576.0, + "y": 223.0, + "width": 468.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 stack size", + "originalText": "the stack size", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "0e1435368efe4833b68a", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "d5760587ac9a843c29f8", + "type": "rectangle", + "x": 560.0, + "y": 267.0, + "width": 500.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": "a97a266d91d3d81da050", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "a97a266d91d3d81da050", + "type": "text", + "x": 576.0, + "y": 283.0, + "width": 468.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": "how many times it has run", + "originalText": "how many times it has run", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "d5760587ac9a843c29f8", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c12bc70aaddbc25a0ee6", + "type": "rectangle", + "x": 0.0, + "y": 344.0, + "width": 1060.0, + "height": 60.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": "832e89dddc63e70dda01", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "832e89dddc63e70dda01", + "type": "text", + "x": 16.0, + "y": 361.5, + "width": 1028.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": "They are equal, and they hash the same. The filename is carried for tracebacks, not identity.", + "originalText": "They are equal, and they hash the same. The filename is carried for tracebacks, not identity.", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c12bc70aaddbc25a0ee6", + "lineHeight": 1.25, + "autoResize": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/f10-inside-a-code-object/diagrams/what-equality-looks-at.svg b/lessons/f10-inside-a-code-object/diagrams/what-equality-looks-at.svg new file mode 100644 index 0000000..e9de231 --- /dev/null +++ b/lessons/f10-inside-a-code-object/diagrams/what-equality-looks-at.svg @@ -0,0 +1 @@ +The same source compiled twice, from two different filescomparedthe name and the argument countsthe flags and the first line numberthe bytecode, in its unspecialized formthe constants, names and side tablesnot comparedthe file it was compiled fromanything the specializer wrote laterthe stack sizehow many times it has runThey are equal, and they hash the same. The filename is carried for tracebacks, not identity. diff --git a/lessons/f10-inside-a-code-object/f10.ipynb b/lessons/f10-inside-a-code-object/f10.ipynb new file mode 100644 index 0000000..4e1aa4c --- /dev/null +++ b/lessons/f10-inside-a-code-object/f10.ipynb @@ -0,0 +1,424 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f10-01", + "metadata": {}, + "source": [ + "# F10. Inside a code object\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/f10-inside-a-code-object/f10.ipynb)\n", + "\n", + "Nine lessons to get here, and what comes out the end is one object you can hold in a variable.\n", + "\n", + "A [code object](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#code-object) is the compiled form of exactly one thing: one module, one function body, one class body. It is a plain record. There is no cleverness in it and nothing lazy about it, and the single most useful thing to know is what is missing: it holds no values. Not the arguments, not the locals, not the result. A code object for a function that has been called a million times is the same object it was before the first call.\n", + "\n", + "Most of it reads exactly how you would expect. One part does not, and it is the part everybody trips over, so most of this lesson is about that.\n", + "\n", + "![the fields of a code object, none of which is a value the program computed](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f10-inside-a-code-object/diagrams/no-values-in-here.svg)" + ] + }, + { + "cell_type": "markdown", + "id": "f10-02", + "metadata": {}, + "source": [ + "## About the source references\n", + "\n", + "Now and then this lesson points at CPython's own source, like this: `Include/cpython/code.h:82-95@v3.15.0rc1`.\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": "f10-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": "f10-04", + "metadata": {}, + "source": [ + "## Which Python is this\n", + "\n", + "Everything below was checked against the version this cell prints and against 3.14. Where the two disagree, the lesson says so." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f10-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": "f10-06", + "metadata": {}, + "source": [ + "## Boxes inside boxes\n", + "\n", + "Compile a file and you get one code object back. Every `def` and every `class` in that file produced a code object too, and each of those went into the enclosing one's `co_consts`, in with the numbers and the strings.\n", + "\n", + "So a module is a tree, and you can walk it with four lines of Python. There is no separate list of functions to consult and no registry anywhere. A nested definition is a constant.\n", + "\n", + "the code objects of a file form a tree, reachable through co_consts alone" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f10-07", + "metadata": {}, + "outputs": [], + "source": [ + "import types\n", + "\n", + "SOURCE = \"\"\"\\\"\\\"\\\"A tiny module.\\\"\\\"\\\"\n", + "\n", + "import math\n", + "\n", + "\n", + "class Shape:\n", + " def area(self):\n", + " return math.pi\n", + "\n", + "\n", + "def counter(n):\n", + " while n:\n", + " yield n\n", + " n = n - 1\n", + "\"\"\"\n", + "\n", + "module = compile(SOURCE, \"tiny.py\", \"exec\")\n", + "\n", + "\n", + "def walk(code, depth=0):\n", + " \"\"\"Every code object reachable from this one, through the constants.\"\"\"\n", + " print(f\" {' ' * depth}{code.co_name:10} qualname {code.co_qualname}\")\n", + " for one in code.co_consts:\n", + " if isinstance(one, types.CodeType):\n", + " walk(one, depth + 1)\n", + "\n", + "\n", + "walk(module)" + ] + }, + { + "cell_type": "markdown", + "id": "f10-08", + "metadata": {}, + "source": [ + "Four code objects out of one file. `co_name` is the short one and `co_qualname` is the path to it, which is how a traceback can say `Shape.area` when two classes both have a method called `area`.\n", + "\n", + "![the code objects of one small file, nested through the constants](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f10-inside-a-code-object/diagrams/boxes-inside-boxes.svg)\n", + "\n", + "Notice what is not in the tree. There is no code object for the class body's `def` line, because a `def` statement is just instructions in the body that made it. And in 3.12 and later a list comprehension is compiled into the function around it rather than into its own object, so writing one adds nothing here.\n", + "\n", + "## Names, in four tuples\n", + "\n", + "Now the confusing part.\n", + "\n", + "A code object has `co_varnames` for locals, `co_cellvars` for locals that a nested function reads, `co_freevars` for names it reads from an enclosing function, and `co_names` for everything looked up by name at run time: globals, attributes, imports.\n", + "\n", + "The first three of those are not stored. There is one array, `co_localsplusnames`, holding every slot a [frame](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#frame) will need, and one string of bytes next to it with one tag per slot. The header says so out loud, [Include/cpython/code.h:82-95@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Include/cpython/code.h#L82-L95), where the four counts are under a comment reading \"redundant values (derived from co_localsplusnames and co_localspluskinds)\".\n", + "\n", + "Getting `co_varnames` runs [Objects/codeobject.c:423-443@v3.15.0rc1#get_localsplus_names](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/codeobject.c#L423-L443), which walks the array and keeps the slots whose tag matches. The test is `(k & kind) == 0`, a bitwise and, and the tags are single bits, [Include/internal/pycore_code.h:192-199@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Include/internal/pycore_code.h#L192-L199). A slot with two bits set comes back from two different calls.\n", + "\n", + "a parameter that a nested function reads is tagged twice, so it appears in two tuples" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f10-09", + "metadata": {}, + "outputs": [], + "source": [ + "def outer(a, b=2, *args, c, **kw):\n", + " n = 0\n", + "\n", + " def inner():\n", + " return a + n\n", + "\n", + " return inner\n", + "\n", + "\n", + "code = outer.__code__\n", + "\n", + "slots = []\n", + "while True:\n", + " try:\n", + " slots.append(code._varname_from_oparg(len(slots)))\n", + " except IndexError:\n", + " break\n", + "\n", + "print(f\" {len(slots)} slots in the array\")\n", + "print(f\" co_varnames has {len(code.co_varnames)}\", end=\"\")\n", + "print(f\", co_cellvars has {len(code.co_cellvars)}\", end=\"\")\n", + "print(f\", co_freevars has {len(code.co_freevars)}\")\n", + "print()\n", + "for i, name in enumerate(slots):\n", + " tags = [\n", + " label\n", + " for label, where in (\n", + " (\"local\", code.co_varnames),\n", + " (\"cell\", code.co_cellvars),\n", + " (\"free\", code.co_freevars),\n", + " )\n", + " if name in where\n", + " ]\n", + " print(f\" slot {i} {name:8} {', '.join(tags)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f10-10", + "metadata": {}, + "source": [ + "Seven slots. Six locals and two cells, which is eight, because `a` is both.\n", + "\n", + "![one array of slots, and the two tuples that overlap on one of them](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f10-inside-a-code-object/diagrams/one-array-four-views.svg)\n", + "\n", + "The reason is a single `|=` in the compiler, [Python/assemble.c:516-528@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Python/assemble.c#L516-L528): every parameter gets `CO_FAST_LOCAL`, and then if the name is also in the cell variables, `CO_FAST_CELL` goes on top. It has to be a local, because that is where the caller puts the argument. It has to be a cell, because the nested function needs to see later changes to it rather than a copy.\n", + "\n", + "If you have ever added `len(co_varnames)` to `len(co_cellvars)` and got a number one too big, that is why.\n", + "\n", + "## The instructions before line one\n", + "\n", + "There is a visible consequence. A slot cannot be a plain value and a cell at the same time, so something has to convert it, and that something is an instruction at the very top of the function.\n", + "\n", + "a function whose parameter is captured starts with instructions that belong to no line of source" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f10-11", + "metadata": {}, + "outputs": [], + "source": [ + "import dis\n", + "\n", + "dis.dis(outer)" + ] + }, + { + "cell_type": "markdown", + "id": "f10-12", + "metadata": {}, + "source": [ + "The first two instructions have `--` where a line number should be. `MAKE_CELL 0` takes the value the caller put in slot 0 and wraps it in a cell, in place. `MAKE_CELL 6` makes an empty cell for `n`, which has no value yet.\n", + "\n", + "![a parameter arriving as a value and being turned into a cell before line one](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f10-inside-a-code-object/diagrams/a-parameter-becomes-a-cell.svg)\n", + "\n", + "Then look at how the closure gets built. `LOAD_FAST_BORROW 0` and `LOAD_FAST_BORROW 6` load the two cell objects, `BUILD_TUPLE` puts them together, and `SET_FUNCTION_ATTRIBUTE 8` hangs that tuple on the new function. Inside `inner`, `COPY_FREE_VARS 2` pulls them out again. Every one of those is an ordinary instruction. There is no hidden machinery.\n", + "\n", + "## co_flags is one integer\n", + "\n", + "`co_flags` is a bit field decided at compile time. Nothing sets a bit in it later.\n", + "\n", + "The definitions are [Include/cpython/code.h:118-153@v3.15.0rc1](https://github.com/python/cpython/blob/v3.15.0rc1/Include/cpython/code.h#L118-L153), and `dis` will give you their names.\n", + "\n", + "a module body and a class body have no flags set at all, and a function has several" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f10-13", + "metadata": {}, + "outputs": [], + "source": [ + "import dis\n", + "\n", + "\n", + "def find(code, name):\n", + " \"\"\"The first code object with this name, anywhere inside this one.\"\"\"\n", + " for one in code.co_consts:\n", + " if isinstance(one, types.CodeType):\n", + " if one.co_name == name:\n", + " return one\n", + " deeper = find(one, name)\n", + " if deeper is not None:\n", + " return deeper\n", + " return None\n", + "\n", + "\n", + "for name in (\"\", \"Shape\", \"area\", \"counter\"):\n", + " found = module if name == \"\" else find(module, name)\n", + " bits = [text for bit, text in sorted(dis.COMPILER_FLAG_NAMES.items()) if found.co_flags & bit]\n", + " print(f\" {name:10} {found.co_flags:>10} {', '.join(bits) or 'nothing set'}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f10-14", + "metadata": {}, + "source": [ + "`CO_OPTIMIZED` is the one that matters most. It means the locals of this code object live in numbered slots rather than in a dictionary, which is why `LOAD_FAST` exists and why you cannot add a local to a running function. A module body does not have it. That is the whole reason module level code is slower than the same code in a function.\n", + "\n", + "![four of the co_flags bits and what each one changes](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f10-inside-a-code-object/diagrams/some-of-the-flags.svg)\n", + "\n", + "`CO_GENERATOR` on `counter` is set because the body contains a `yield`. The compiler decided that while walking the tree, four lessons ago, and calling `counter` returns a generator instead of running the body because of that one bit.\n", + "\n", + "## Two of them can be equal\n", + "\n", + "Code objects compare by value, and the list of what gets compared is worth reading, because of what is missing from it: [Objects/codeobject.c:2502-2541@v3.15.0rc1#code_richcompare](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/codeobject.c#L2502-L2541) checks the name, the argument counts, the flags, the first line number, the bytecode, the constants, the names and both side tables. It never looks at `co_filename`.\n", + "\n", + "The bytecode comparison is the other interesting bit. It reads through `_Py_GetBaseCodeUnit`, which undoes [specialization](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#specialization), so a function that has been running hot for an hour still compares equal to a freshly compiled copy of itself.\n", + "\n", + "the same source compiled from two different filenames gives two equal code objects" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f10-15", + "metadata": {}, + "outputs": [], + "source": [ + "SOURCE = \"def h(x):\\n return x * 2\\n\"\n", + "\n", + "here = compile(SOURCE, \"/one/place.py\", \"exec\").co_consts[0]\n", + "there = compile(SOURCE, \"/somewhere/else.py\", \"exec\").co_consts[0]\n", + "\n", + "print(f\" filenames {here.co_filename} and {there.co_filename}\")\n", + "print(f\" equal {here == there}\")\n", + "print(f\" same hash {hash(here) == hash(there)}\")\n", + "print(f\" same object {here is there}\")\n", + "print()\n", + "\n", + "namespace = {}\n", + "exec(compile(SOURCE, \"/one/place.py\", \"exec\"), namespace)\n", + "for _ in range(5000):\n", + " namespace[\"h\"](3)\n", + "\n", + "warm = namespace[\"h\"].__code__\n", + "print(f\" after 5000 calls, still equal to a fresh compile {warm == there}\")\n", + "print(f\" co_code is byte for byte identical {warm.co_code == there.co_code}\")\n", + "adaptive_differs = warm._co_code_adaptive != there._co_code_adaptive\n", + "print(f\" the adaptive copy underneath it is not {adaptive_differs}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f10-16", + "metadata": {}, + "source": [ + "The filename is carried so a traceback can tell you where to look. It is not part of what the object is.\n", + "\n", + "![the fields equality reads and the ones it ignores](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f10-inside-a-code-object/diagrams/what-equality-looks-at.svg)\n", + "\n", + "The last two lines are a preview of F14. `co_code` gives you the instructions the compiler produced. `_co_code_adaptive` gives you the ones the interpreter is actually running, which change as it learns. Equality uses the first, which is the only sensible choice, since otherwise a function would stop being equal to itself partway through a loop.\n", + "\n", + "## You cannot change one\n", + "\n", + "Code objects are immutable. There is no setter for any of the `co_` attributes. What there is instead is `replace`, which builds a new one with some fields swapped, and it is how tools like `coverage` and `cloudpickle` get their work done.\n", + "\n", + "replace gives you a new code object and leaves the original alone" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f10-17", + "metadata": {}, + "outputs": [], + "source": [ + "renamed = here.replace(co_name=\"something_else\", co_filename=\"/made/up.py\")\n", + "\n", + "print(f\" original {here.co_name:16} {here.co_filename}\")\n", + "print(f\" new {renamed.co_name:16} {renamed.co_filename}\")\n", + "print(f\" equal to the original now {renamed == here}\")\n", + "print()\n", + "try:\n", + " here.co_name = \"nope\"\n", + "except AttributeError as problem:\n", + " print(f\" assigning to co_name says: {problem}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f10-18", + "metadata": {}, + "source": [ + "Changing the name broke equality, because the name is compared. Changing the filename would not have.\n", + "\n", + "Everything a new code object goes through on the way in is [Objects/codeobject.c:446-485@v3.15.0rc1#_PyCode_Validate](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/codeobject.c#L446-L485), which checks the argument counts against each other, checks the bytecode is an even number of bytes, and refuses anything that does not add up. That is the guard between a valid code object and a crash, and it is worth knowing it exists before you go building one by hand.\n", + "\n", + "## Try it yourself\n", + "\n", + "1. Walk the code objects of a real module from the standard library. Which one has the most constants?\n", + "2. Find a function where `co_names` and `co_varnames` share a name. What has to be true for that?\n", + "3. Compile a lambda. What is its `co_name`, and what is its `co_qualname`?\n", + "4. Use `replace` to change `co_consts` of a simple function and build a working function from the result with `types.FunctionType`.\n", + "5. Set `CO_OPTIMIZED` on a module body with `replace` and try to run it. What happens, and why is that fair?\n", + "\n", + "## What just happened\n", + "\n", + "A code object is the compiled form of one module, function body or class body, and it holds no values at all. That is what lets one of them serve every call.\n", + "\n", + "Compiling a file gives you one code object with the rest nested inside it, through `co_consts`. There is no other index. A nested `def` is a constant.\n", + "\n", + "Names are the confusing part. `co_varnames`, `co_cellvars` and `co_freevars` are not stored. They are filtered out of one array of slots using one tag byte per slot, and a tag byte can have more than one bit set, so a parameter that a nested function reads appears in two of them and gets counted twice.\n", + "\n", + "That double tagging is visible from the outside as `MAKE_CELL` instructions at the top of a function, which have no line number because they belong to no line you wrote.\n", + "\n", + "`co_flags` is a bit field fixed at compile time. `CO_OPTIMIZED` is the one that decides whether locals are slots or dictionary entries, and a module body does not have it.\n", + "\n", + "Two code objects compiled from different files can be equal and hash the same, because the filename is not part of the comparison. Neither is anything the specializer wrote after compilation.\n", + "\n", + "## What is next\n", + "\n", + "F11 is the two side tables this lesson skipped: the [line table](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#line-table) and the [exception table](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#exception-table). Both are compressed byte formats, both are decodable by hand, and one of them explains why a `try` block costs nothing until something goes wrong." + ] + } + ], + "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 bad3b66..cf9c335 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.011s\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.002s\n\nFAILED (failures=1)\n\n3 tests run, 1 failed, 0 errored\n" }, { "name": "b03-10", @@ -514,6 +514,50 @@ } ] }, + { + "slug": "f10-inside-a-code-object", + "cells": [ + { + "name": "f10-03", + "status": "ok" + }, + { + "name": "f10-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": "f10-07", + "status": "ok", + "printed": " qualname \n Shape qualname Shape\n area qualname Shape.area\n counter qualname counter\n" + }, + { + "name": "f10-09", + "status": "ok", + "printed": " 7 slots in the array\n co_varnames has 6, co_cellvars has 2, co_freevars has 0\n\n slot 0 a local, cell\n slot 1 b local\n slot 2 c local\n slot 3 args local\n slot 4 kw local\n slot 5 inner local\n slot 6 n cell\n" + }, + { + "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" + }, + { + "name": "f10-13", + "status": "ok", + "printed": " 0 nothing set\n Shape 0 nothing set\n area 134217731 OPTIMIZED, NEWLOCALS, METHOD\n counter 35 OPTIMIZED, NEWLOCALS, GENERATOR\n" + }, + { + "name": "f10-15", + "status": "ok", + "printed": " filenames /one/place.py and /somewhere/else.py\n equal True\n same hash True\n same object False\n\n after 5000 calls, still equal to a fresh compile True\n co_code is byte for byte identical True\n the adaptive copy underneath it is not True\n" + }, + { + "name": "f10-17", + "status": "ok", + "printed": " original h /one/place.py\n new something_else /made/up.py\n equal to the original now False\n\n assigning to co_name says: readonly attribute\n" + } + ] + }, { "slug": "t01-one-line-seven-stages", "cells": [ @@ -896,7 +940,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", @@ -1173,7 +1217,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", @@ -1207,12 +1251,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 0x13246b0, 3 reference(s), 26 bytes, not tracked\nlist at 0x1707020, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x12cbf00, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x164ab60, 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 0x15374b8, 3 reference(s), 26 bytes, not tracked\nlist at 0x1685d38, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x1825e68, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x14d0058, 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) 0x17122b0\nid(b) 0x17ff710\nid(c) 0x17122b0\n" + "printed": "a == b True same contents\na is b False different objects\na is c True same object\n\nid(a) 0xfe7c48\nid(b) 0x12976b0\nid(c) 0xfe7c48\n" }, { "name": "t08-17", @@ -1252,7 +1296,7 @@ { "name": "t08-35", "status": "ok", - "printed": "dict at 0x11e59e0\ndict at 0x136e6f0\nlist at 0x17f7a00\n" + "printed": "dict at 0x1342e70\ndict at 0x16f4028\nlist at 0x12975c8\n" }, { "name": "t08-37", @@ -1311,7 +1355,7 @@ { "name": "t09-21", "status": "ok", - "printed": "Node at 0x1752620 -> Node at 0x177c330 -> Node at 0x16231a8 -> Node at 0x1752620\n" + "printed": "Node at 0x135a0e0 -> Node at 0x135d318 -> Node at 0x1521170 -> Node at 0x135a0e0\n" }, { "name": "t09-23", @@ -1341,7 +1385,7 @@ { "name": "t09-37", "status": "ok", - "printed": "first object was at 0x164b820\nsecond object is at 0x164b820\nsame address reused -> True\n" + "printed": "first object was at 0x14b3670\nsecond object is at 0x14b3670\nsame address reused -> True\n" } ] }, diff --git a/probes/pyodide/lessons.md b/probes/pyodide/lessons.md index cf53fe7..e5d160e 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. -25 lesson(s) on Pyodide 3.14.2: 25 ran end to end, 272 cell(s) in total. +26 lesson(s) on Pyodide 3.14.2: 26 ran end to end, 280 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. @@ -21,6 +21,7 @@ The checks in `report.md` next to this ask whether a surface exists. This runs t | f07-the-list-becomes-a-graph | 8 | runs end to end | | f08-the-optimizer | 7 | runs end to end | | f09-two-bytes-at-a-time | 7 | runs end to end | +| f10-inside-a-code-object | 8 | 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 |