diff --git a/GLOSSARY.md b/GLOSSARY.md index 72fddf3..f840a5f 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -8,7 +8,7 @@ This file is generated from `pyxray/src/pyxray/glossary.py`. Edit that and run ` ## Index -[ASDL](#asdl) | [Argument Clinic](#argument-clinic) | [C stack](#c-stack) | [C3 linearization](#c3-linearization) | [DEBUG_SAVEALL](#debug_saveall) | [DISABLE](#disable) | [EXTENDED_ARG](#extended_arg) | [GC pre header](#gc-pre-header) | [GIL](#gil) | [JIT](#jit) | [MRO](#mro) | [PEG parser](#peg-parser) | [PYTHONMALLOC](#pythonmalloc) | [PyVarObject](#pyvarobject) | [Pyodide](#pyodide) | [WebAssembly](#webassembly) | [abi flags](#abi-flags) | [abstract interpreter](#abstract-interpreter) | [abstract syntax tree](#abstract-syntax-tree) | [adaptive counter](#adaptive-counter) | [adaptive instruction](#adaptive-instruction) | [allocator domain](#allocator-domain) | [arena](#arena) | [assembler](#assembler) | [asynchronous exception](#asynchronous-exception) | [attach and detach](#attach-and-detach) | [backtrace](#backtrace) | [basic block](#basic-block) | [biased reference counting](#biased-reference-counting) | [binding](#binding) | [block](#block) | [blurb](#blurb) | [borrowed reference](#borrowed-reference) | [bound method](#bound-method) | [bytecode](#bytecode) | [cached hash](#cached-hash) | [calling convention](#calling-convention) | [cases generator](#cases-generator) | [cell](#cell) | [class cell](#class-cell) | [closure](#closure) | [code generation](#code-generation) | [code object](#code-object) | [code point](#code-point) | [code unit](#code-unit) | [coding cookie](#coding-cookie) | [cold block](#cold-block) | [collection threshold](#collection-threshold) | [compact dict](#compact-dict) | [compact int](#compact-int) | [compact string](#compact-string) | [computed goto](#computed-goto) | [configure](#configure) | [constant folding](#constant-folding) | [control flow graph](#control-flow-graph) | [copy and patch](#copy-and-patch) | [critical section](#critical-section) | [cross interpreter data](#cross-interpreter-data) | [cycle collector](#cycle-collector) | [daemon thread](#daemon-thread) | [data descriptor](#data-descriptor) | [data stack](#data-stack) | [deallocation](#deallocation) | [debug build](#debug-build) | [debug hooks](#debug-hooks) | [deferred reference counting](#deferred-reference-counting) | [deoptimization](#deoptimization) | [descriptor](#descriptor) | [devguide](#devguide) | [digit array](#digit-array) | [dispatch](#dispatch) | [dispatch table](#dispatch-table) | [environment changed](#environment-changed) | [eval breaker](#eval-breaker) | [eval loop](#eval-loop) | [evaluation order](#evaluation-order) | [exact type check](#exact-type-check) | [exception table](#exception-table) | [executor](#executor) | [f string](#f-string) | [finalized bit](#finalized-bit) | [finalizer](#finalizer) | [frame](#frame) | [frame object](#frame-object) | [free list](#free-list) | [free threaded build](#free-threaded-build) | [free variable](#free-variable) | [gdb](#gdb) | [generated file](#generated-file) | [generation](#generation) | [grammar](#grammar) | [guard](#guard) | [header file](#header-file) | [heap type](#heap-type) | [immortal object](#immortal-object) | [immortalization](#immortalization) | [indent and dedent](#indent-and-dedent) | [inline cache](#inline-cache) | [inline values](#inline-values) | [instance dictionary](#instance-dictionary) | [instruction](#instruction) | [instruction DSL](#instruction-dsl) | [instruction pointer](#instruction-pointer) | [instrumented instruction](#instrumented-instruction) | [interning](#interning) | [interpreter pool](#interpreter-pool) | [interpreter state](#interpreter-state) | [left recursion](#left-recursion) | [line table](#line-table) | [magic number](#magic-number) | [mark alive pass](#mark-alive-pass) | [marshal](#marshal) | [metaclass](#metaclass) | [micro operation](#micro-operation) | [mimalloc](#mimalloc) | [mimalloc heap](#mimalloc-heap) | [monitoring events](#monitoring-events) | [new reference](#new-reference) | [object](#object) | [object header](#object-header) | [obmalloc](#obmalloc) | [oparg](#oparg) | [opcode](#opcode) | [optimistic read](#optimistic-read) | [over allocation](#over-allocation) | [ownership](#ownership) | [parser generator](#parser-generator) | [pdb](#pdb) | [pending call](#pending-call) | [per object lock](#per-object-lock) | [periodic check](#periodic-check) | [permanent generation](#permanent-generation) | [pointer](#pointer) | [pool](#pool) | [pool header](#pool-header) | [probe sequence](#probe-sequence) | [product type](#product-type) | [profile guided optimization](#profile-guided-optimization) | [pseudo instruction](#pseudo-instruction) | [pyc file](#pyc-file) | [pyconfig](#pyconfig) | [quickening](#quickening) | [race condition](#race-condition) | [reentrancy](#reentrancy) | [reference count](#reference-count) | [reference count contention](#reference-count-contention) | [reference cycle](#reference-cycle) | [reference leak](#reference-leak) | [referrer](#referrer) | [regen](#regen) | [regrtest](#regrtest) | [replacement field](#replacement-field) | [resource](#resource) | [resurrection](#resurrection) | [safe memory reclamation](#safe-memory-reclamation) | [scope](#scope) | [segment](#segment) | [segmentation fault](#segmentation-fault) | [shareable object](#shareable-object) | [short circuiting](#short-circuiting) | [side exit](#side-exit) | [signal handler](#signal-handler) | [single character cache](#single-character-cache) | [size class](#size-class) | [slot](#slot) | [slot wrapper](#slot-wrapper) | [small int cache](#small-int-cache) | [small integer cache](#small-integer-cache) | [soft keyword](#soft-keyword) | [specialization](#specialization) | [specialization family](#specialization-family) | [split table](#split-table) | [stack depth](#stack-depth) | [stack effect](#stack-effect) | [stack reference](#stack-reference) | [stateless function](#stateless-function) | [static type](#static-type) | [stencil](#stencil) | [stolen reference](#stolen-reference) | [stop the world](#stop-the-world) | [string kind](#string-kind) | [struct](#struct) | [subinterpreter](#subinterpreter) | [sum type](#sum-type) | [switch interval](#switch-interval) | [symbol table](#symbol-table) | [symbol table pass](#symbol-table-pass) | [t string](#t-string) | [tagged integer](#tagged-integer) | [tagged pointer](#tagged-pointer) | [tail call](#tail-call) | [test case](#test-case) | [thread safety declaration](#thread-safety-declaration) | [thread state](#thread-state) | [tier one](#tier-one) | [tier two](#tier-two) | [token](#token) | [tokenizer](#tokenizer) | [tool id](#tool-id) | [trace](#trace) | [trace function](#trace-function) | [traceback](#traceback) | [transient GIL](#transient-gil) | [type object](#type-object) | [underflow](#underflow) | [unwinding](#unwinding) | [value stack](#value-stack) | [varint](#varint) | [watcher](#watcher) | [weak reference](#weak-reference) | [weakref callback](#weakref-callback) | [weakref offset](#weakref-offset) | [zero cost exceptions](#zero-cost-exceptions) +[ASDL](#asdl) | [Argument Clinic](#argument-clinic) | [C stack](#c-stack) | [C3 linearization](#c3-linearization) | [DEBUG_SAVEALL](#debug_saveall) | [DISABLE](#disable) | [EXTENDED_ARG](#extended_arg) | [GC pre header](#gc-pre-header) | [GIL](#gil) | [JIT](#jit) | [MRO](#mro) | [PEG parser](#peg-parser) | [PYTHONMALLOC](#pythonmalloc) | [PyVarObject](#pyvarobject) | [Pyodide](#pyodide) | [WebAssembly](#webassembly) | [abi flags](#abi-flags) | [abstract interpreter](#abstract-interpreter) | [abstract syntax tree](#abstract-syntax-tree) | [adaptive counter](#adaptive-counter) | [adaptive instruction](#adaptive-instruction) | [allocator domain](#allocator-domain) | [arena](#arena) | [assembler](#assembler) | [asynchronous exception](#asynchronous-exception) | [attach and detach](#attach-and-detach) | [backtrace](#backtrace) | [basic block](#basic-block) | [biased reference counting](#biased-reference-counting) | [binding](#binding) | [block](#block) | [blurb](#blurb) | [borrowed reference](#borrowed-reference) | [bound method](#bound-method) | [bytecode](#bytecode) | [cached hash](#cached-hash) | [calling convention](#calling-convention) | [cases generator](#cases-generator) | [cell](#cell) | [class cell](#class-cell) | [closure](#closure) | [code generation](#code-generation) | [code object](#code-object) | [code point](#code-point) | [code unit](#code-unit) | [coding cookie](#coding-cookie) | [cold block](#cold-block) | [collection threshold](#collection-threshold) | [compact dict](#compact-dict) | [compact int](#compact-int) | [compact string](#compact-string) | [computed goto](#computed-goto) | [configure](#configure) | [constant folding](#constant-folding) | [control flow graph](#control-flow-graph) | [copy and patch](#copy-and-patch) | [critical section](#critical-section) | [cross interpreter data](#cross-interpreter-data) | [cycle collector](#cycle-collector) | [daemon thread](#daemon-thread) | [data descriptor](#data-descriptor) | [data stack](#data-stack) | [deallocation](#deallocation) | [debug build](#debug-build) | [debug hooks](#debug-hooks) | [deferred reference counting](#deferred-reference-counting) | [deoptimization](#deoptimization) | [descriptor](#descriptor) | [devguide](#devguide) | [digit array](#digit-array) | [dispatch](#dispatch) | [dispatch table](#dispatch-table) | [environment changed](#environment-changed) | [eval breaker](#eval-breaker) | [eval loop](#eval-loop) | [evaluation order](#evaluation-order) | [exact type check](#exact-type-check) | [exception table](#exception-table) | [executor](#executor) | [f string](#f-string) | [finalized bit](#finalized-bit) | [finalizer](#finalizer) | [frame](#frame) | [frame object](#frame-object) | [free list](#free-list) | [free threaded build](#free-threaded-build) | [free variable](#free-variable) | [gdb](#gdb) | [generated file](#generated-file) | [generation](#generation) | [grammar](#grammar) | [guard](#guard) | [header file](#header-file) | [heap type](#heap-type) | [immortal object](#immortal-object) | [immortalization](#immortalization) | [indent and dedent](#indent-and-dedent) | [inline cache](#inline-cache) | [inline values](#inline-values) | [instance dictionary](#instance-dictionary) | [instruction](#instruction) | [instruction DSL](#instruction-dsl) | [instruction pointer](#instruction-pointer) | [instrumented instruction](#instrumented-instruction) | [interning](#interning) | [interpreter pool](#interpreter-pool) | [interpreter state](#interpreter-state) | [left recursion](#left-recursion) | [line table](#line-table) | [magic number](#magic-number) | [mark alive pass](#mark-alive-pass) | [marshal](#marshal) | [metaclass](#metaclass) | [micro operation](#micro-operation) | [mimalloc](#mimalloc) | [mimalloc heap](#mimalloc-heap) | [monitoring events](#monitoring-events) | [new reference](#new-reference) | [object](#object) | [object header](#object-header) | [obmalloc](#obmalloc) | [oparg](#oparg) | [opcode](#opcode) | [optimistic read](#optimistic-read) | [over allocation](#over-allocation) | [ownership](#ownership) | [parser generator](#parser-generator) | [path configuration](#path-configuration) | [pdb](#pdb) | [pending call](#pending-call) | [per object lock](#per-object-lock) | [periodic check](#periodic-check) | [permanent generation](#permanent-generation) | [pointer](#pointer) | [pool](#pool) | [pool header](#pool-header) | [probe sequence](#probe-sequence) | [product type](#product-type) | [profile guided optimization](#profile-guided-optimization) | [pseudo instruction](#pseudo-instruction) | [pyc file](#pyc-file) | [pyconfig](#pyconfig) | [quickening](#quickening) | [race condition](#race-condition) | [reentrancy](#reentrancy) | [reference count](#reference-count) | [reference count contention](#reference-count-contention) | [reference cycle](#reference-cycle) | [reference leak](#reference-leak) | [referrer](#referrer) | [regen](#regen) | [regrtest](#regrtest) | [replacement field](#replacement-field) | [resource](#resource) | [resurrection](#resurrection) | [safe memory reclamation](#safe-memory-reclamation) | [safe path](#safe-path) | [scope](#scope) | [segment](#segment) | [segmentation fault](#segmentation-fault) | [shareable object](#shareable-object) | [short circuiting](#short-circuiting) | [side exit](#side-exit) | [signal handler](#signal-handler) | [single character cache](#single-character-cache) | [size class](#size-class) | [slot](#slot) | [slot wrapper](#slot-wrapper) | [small int cache](#small-int-cache) | [small integer cache](#small-integer-cache) | [soft keyword](#soft-keyword) | [specialization](#specialization) | [specialization family](#specialization-family) | [split table](#split-table) | [stack depth](#stack-depth) | [stack effect](#stack-effect) | [stack reference](#stack-reference) | [stateless function](#stateless-function) | [static type](#static-type) | [stencil](#stencil) | [stolen reference](#stolen-reference) | [stop the world](#stop-the-world) | [string kind](#string-kind) | [struct](#struct) | [subinterpreter](#subinterpreter) | [sum type](#sum-type) | [switch interval](#switch-interval) | [symbol table](#symbol-table) | [symbol table pass](#symbol-table-pass) | [t string](#t-string) | [tagged integer](#tagged-integer) | [tagged pointer](#tagged-pointer) | [tail call](#tail-call) | [test case](#test-case) | [thread safety declaration](#thread-safety-declaration) | [thread state](#thread-state) | [tier one](#tier-one) | [tier two](#tier-two) | [token](#token) | [tokenizer](#tokenizer) | [tool id](#tool-id) | [trace](#trace) | [trace function](#trace-function) | [traceback](#traceback) | [transient GIL](#transient-gil) | [two phase initialisation](#two-phase-initialisation) | [type object](#type-object) | [underflow](#underflow) | [unwinding](#unwinding) | [value stack](#value-stack) | [varint](#varint) | [watcher](#watcher) | [weak reference](#weak-reference) | [weakref callback](#weakref-callback) | [weakref offset](#weakref-offset) | [zero cost exceptions](#zero-cost-exceptions) ## Reading the source @@ -1578,6 +1578,34 @@ Also written `Interpreter.call`, `_PyFunction_GetXIData`. First met in C08. See Also written `concurrent.futures.InterpreterPoolExecutor`. First met in C08. See also [subinterpreter](#subinterpreter), [cross interpreter data](#cross-interpreter-data). +## Starting up and shutting down + +The words for what happens before your first line runs and after your last one does. R01 through R09 are the lessons, and most of what they cover has already happened by the time you get a prompt. + +### two phase initialisation + +**Startup split in half, because the first half cannot import anything.** + +Core initialisation builds the runtime, the main interpreter and the built in types, and it has to work with no import system, no `sys.path` and no encodings. Main initialisation is everything that needs those, including computing `sys.path` itself. The split is why a configuration error can be reported as a fatal error with a plain C string rather than as a Python exception: at that point there is nothing to raise. + +Also written `pyinit_core`, `pyinit_main`, `Py_InitializeFromConfig`. First met in R01. See also [path configuration](#path-configuration), [safe path](#safe-path). In the source: [`Python/pylifecycle.c:1609-1639@v3.15.0rc1#Py_InitializeFromConfig`](https://github.com/python/cpython/blob/v3.15.0rc1/Python/pylifecycle.c#L1609-L1639). + +### path configuration + +**The set of paths worked out at startup, of which sys.path is the visible part.** + +`sys.prefix`, `sys.exec_prefix`, `sys.executable`, `sys._base_executable` and `sys.path` are all decided together by one program, `Modules/getpath.py`, which is compiled to bytecode at build time and evaluated during main initialisation. It is written in Python and it is not on `sys.path`, because `sys.path` is what it is there to produce, so it is handed eleven C functions to stand in for the `os.path` it cannot import. + +Also written `Modules/getpath.py`, `_PyConfig_InitPathConfig`. First met in R01. See also [two phase initialisation](#two-phase-initialisation), [safe path](#safe-path). In the source: [`Modules/getpath.c:855-894@v3.15.0rc1#_PyConfig_InitPathConfig`](https://github.com/python/cpython/blob/v3.15.0rc1/Modules/getpath.c#L855-L894). + +### safe path + +**The switch that stops the script's own directory going on the front of sys.path.** + +`-P` on the command line or `PYTHONSAFEPATH` in the environment. Without it the interpreter puts the script's directory, or the empty string for `-c` and `-m`, at `sys.path[0]` after everything else is already set up, which is what makes a file called `random.py` next to your script shadow the standard library. With it, that entry is simply not added. + +Also written `-P`, `PYTHONSAFEPATH`, `config->safe_path`. First met in R01. See also [path configuration](#path-configuration). In the source: [`Modules/main.c:696-728@v3.15.0rc1#safe_path`](https://github.com/python/cpython/blob/v3.15.0rc1/Modules/main.c#L696-L728). + ## Building the interpreter The words that turn out to be about the binary rather than about the language. B01 through B04 are the lessons, and several numbers in the earlier lessons move when the build does. diff --git a/README.md b/README.md index 2192f95..4f8b4c8 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ A lesson can also end with a boss fight, which is a problem the text does not so | C06 | [Four threads reading the same list](lessons/c06-reading-without-a-lock/c06.ipynb) | What replaced the GIL on the read path, and the thing that decides whether removing it helps at all. Reads of a list, a dict and a set are all lock free in 3.15 and all three do the same optimistic read, which is load the pointer, try to add one to the object's count and load the pointer again to see whether it moved, with the object's critical section kept as the retry rather than the road. Then the half nobody expects: once reads are lock free the bottleneck is the reference count itself, because four cores writing to one word take turns in hardware whatever Python does, so four threads reading a list of small integers get three and a half times the work of one thread and the same code over a list of ordinary objects gets a third of it. Immortal objects have no count to contend for, which is why the free threaded build immortalizes every constant it compiles, including strings and numbers the ordinary build leaves alone. And the storage a writer replaces cannot be freed while a reader might still be holding it, so it goes on a queue and is handed back at the periodic check from C05 | M8 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/c06-reading-without-a-lock/c06.ipynb) | | C07 | [The lock that comes back](lessons/c07-the-lock-that-comes-back/c07.ipynb) | Whether a build was compiled without the GIL and whether the GIL is on right now are two different questions, and the second one can change while your program runs. Four ways to ask for a lock state at startup, two on the command line and two in the environment, with an ordinary build refusing to start at all rather than quietly ignoring a request to drop the lock. Then the way it comes back on that nobody asked for: importing a compiled module turns the lock on before running its init function, because the runtime cannot ask whether a module is safe until after that function has run, and a module that never declared Py_mod_gil leaves the lock on for the rest of the process with one warning naming it. Behind the switch is a counter rather than a flag, and raising it from zero stops every thread first. The second half is the bill, eight single threaded workloads on both builds, slower without the lock on anything that touches many objects and faster on anything dominated by allocation, because removing the lock also swapped pymalloc for mimalloc | M8 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/c07-the-lock-that-comes-back/c07.ipynb) | | C08 | [Sending work to another interpreter](lessons/c08-sending-work-to-another-interpreter/c08.ipynb) | C04 made a second interpreter and found that the two share almost nothing, which leaves the question of how you give one of them a job. Interpreter.call sends a function by sending its code, so it only works on a function that reads no globals and closes over nothing, which is why a recursive function written at the top level will not cross. Arguments go through a chain of three routes tried in order, a purpose written one for a short list of types, then the function route, then pickle, and a lambda proves the middle one exists by failing the other two and crossing anyway. An exception from over there arrives as an ExecutionFailed carrying a snapshot rather than the exception itself, so an existing except clause will not catch what it looks like it catches. A compiled module has to declare that it is safe in more than one interpreter and the import is refused outright if it says nothing, which is stricter than the equivalent rule for the lock. Then the cost: a queue does over a million round trips a second for a small int and about a hundred thousand for a hundred item list, and two workloads split three ways on both builds show interpreters winning the compute job by two and a half times on a build with the lock, losing it to plain threads on a build without one, and losing the data heavy job by forty two times on both | M8 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/c08-sending-work-to-another-interpreter/c08.ipynb) | +| R01 | [Before your first line](lessons/r01-before-your-first-line/r01.ipynb) | Everything the interpreter has already done by the time your first statement runs. A fresh interpreter has a few dozen modules in sys.modules and, with site out of the way, not one of them was read from a file, because they are either C compiled into the binary or Python bytecode frozen into it. Startup comes in two halves on purpose, and the first half has no import system and no sys.path, which is why a configuration mistake comes out as a fatal error with a plain C string rather than as a traceback. Settings arrive from the command line, the environment and the embedder, and the answer is the highest number anybody asked for rather than whichever one is nearest, so PYTHONOPTIMIZE=2 is not undone by passing -O. sys.path itself is produced by a Python program that is frozen into the binary and handed eleven C functions to stand in for the os.path it cannot import, and its output always includes a zip file that usually does not exist. The front of sys.path is pushed on last, after startup is over, which is the whole mechanism behind a file called random.py shadowing the standard library, and -P turns it off. Two recordings then compare a release build against a debug one and find that the extra time is mostly not the assertions, it is the fourteen modules a debug build reads off the disk because it turns frozen modules off | M8 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/r01-before-your-first-line/r01.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 708eb2e..f0f36dc 100644 --- a/citations.lock.json +++ b/citations.lock.json @@ -1375,16 +1375,56 @@ "first_line": "static PyObject *", "lines": 14 }, + "Modules/getpath.c:560-582@v3.15.0rc1": { + "digest": "bacacbf121ce39c5", + "first_line": "#endif", + "lines": 23 + }, + "Modules/getpath.c:830-840@v3.15.0rc1": { + "digest": "30c0ddbe82894ce3", + "first_line": "", + "lines": 11 + }, + "Modules/getpath.c:855-894@v3.15.0rc1": { + "digest": "0f1a2db7ace0f0ec", + "first_line": "PyStatus", + "lines": 40 + }, + "Modules/main.c:37-76@v3.15.0rc1": { + "digest": "0290f26f3c232762", + "first_line": "pymain_init(const _PyArgv *args)", + "lines": 40 + }, "Modules/main.c:446-446@v3.15.0rc1": { "digest": "d5c57b74dc318fbb", "first_line": "pymain_run_file(const PyConfig *config)", "lines": 1 }, + "Modules/main.c:657-696@v3.15.0rc1": { + "digest": "3b8b28f1f23a697a", + "first_line": "static void", + "lines": 40 + }, + "Modules/main.c:696-728@v3.15.0rc1": { + "digest": "38ac6f2f84817165", + "first_line": "PyObject *path0 = NULL;", + "lines": 33 + }, "Modules/main.c:830-852@v3.15.0rc1": { "digest": "b15e6301c9c95772", "first_line": "int", "lines": 23 }, + "Modules/main.c:831-852@v3.15.0rc1": { + "digest": "2980a854547fda9d", + "first_line": "Py_RunMain(void)", + "lines": 22 + }, + "Modules/main.c:855-868@v3.15.0rc1": { + "digest": "8c43d6304d18923c", + "first_line": "static int", + "lines": 14 + }, "Modules/posixmodule.c:8660-8672@v3.15.0rc1": { "digest": "3f3960989b85426a", "first_line": "if (_PyInterpreterState_GetFinalizing(interp) != NULL) {", @@ -4210,6 +4250,11 @@ "first_line": "int", "lines": 21 }, + "Python/initconfig.c:1193-1201@v3.15.0rc1": { + "digest": "0cd6d8c3fdac2d2a", + "first_line": "#ifdef MS_WINDOWS", + "lines": 9 + }, "Python/initconfig.c:1970-1990@v3.15.0rc1": { "digest": "19e00e6a2eee5295", "first_line": "static PyStatus", @@ -4220,6 +4265,21 @@ "first_line": "config_read_gil(PyConfig *config, size_t len, wchar_t first_char)", "lines": 30 }, + "Python/initconfig.c:1995-2020@v3.15.0rc1": { + "digest": "a936b285993c1090", + "first_line": "{", + "lines": 26 + }, + "Python/initconfig.c:2849-2866@v3.15.0rc1": { + "digest": "ea23f8c25d79e425", + "first_line": "/* -X frozen_modules=[on|off] */", + "lines": 18 + }, + "Python/initconfig.c:3210-3222@v3.15.0rc1": { + "digest": "cf83f51eca363edc", + "first_line": "case 'E':", + "lines": 13 + }, "Python/instrumentation.c:1190-1216@v3.15.0rc1": { "digest": "cea5143d2a582d2a", "first_line": "size_t nargsf = (size_t) nargs | PY_VECTORCALL_ARGUMENTS_OFFSET;", @@ -4335,6 +4395,21 @@ "first_line": "Logically, all symbols begin as UNKNOWN, and can transition downwards along the", "lines": 24 }, + "Python/preconfig.c:560-578@v3.15.0rc1": { + "digest": "83631839f7dcfc98", + "first_line": "}", + "lines": 19 + }, + "Python/pylifecycle.c:1150-1172@v3.15.0rc1": { + "digest": "b9c5af5cdd32a97b", + "first_line": "", + "lines": 23 + }, + "Python/pylifecycle.c:1589-1606@v3.15.0rc1": { + "digest": "f26bda3613cf2a5c", + "first_line": "static PyStatus", + "lines": 18 + }, "Python/pylifecycle.c:1609-1639@v3.15.0rc1": { "digest": "332183fc478125a5", "first_line": "PyStatus", diff --git a/experiments/README.md b/experiments/README.md index f4663d0..13b10b2 100644 --- a/experiments/README.md +++ b/experiments/README.md @@ -36,6 +36,8 @@ So those programs run somewhere else. They run in the images this project publis | [c07-what-one-thread-pays-with-the-lock](tier1/c07-what-one-thread-pays-with-the-lock.md) | C07 | release | What do the same eight workloads cost on a build that kept the lock? | | [c08-three-ways-to-split-the-work](tier1/c08-three-ways-to-split-the-work.md) | C08 | release | Does handing work to another interpreter pay, and what decides whether it does? | | [c08-three-ways-without-the-lock](tier1/c08-three-ways-without-the-lock.md) | C08 | freethreaded | Once plain threads run in parallel too, is there anything left for interpreters? | +| [r01-what-startup-costs](tier1/r01-what-startup-costs.md) | R01 | release | How much is already loaded before your program starts, and how long did that take? | +| [r01-what-startup-costs-on-a-debug-build](tier1/r01-what-startup-costs-on-a-debug-build.md) | R01 | debug | Does a debug build do more work at startup, or the same work more slowly? | ## The commands diff --git a/experiments/tier1/r01-what-startup-costs-on-a-debug-build.md b/experiments/tier1/r01-what-startup-costs-on-a-debug-build.md new file mode 100644 index 0000000..71f4f5a --- /dev/null +++ b/experiments/tier1/r01-what-startup-costs-on-a-debug-build.md @@ -0,0 +1,125 @@ +# The same startup on a build with the assertions left in + +Generated by `just build-tier1`. Do not edit by hand, the change will be overwritten. + +Does a debug build do more work at startup, or the same work more slowly? + +- Lesson: R01 +- Build: debug +- Image: ghcr.io/tamnd/cpython-internals/cpython:debug@sha256:7baea8f3dd4de2e4c3b020543729b147e636494ae9758dabffb4675793e37170 +- Interpreter: 3.15.0rc1 (37e98da:37e98da, Aug 29 2026, 09:24:21) [GCC 14.2.0] +- Recorded: 2026-09-06 + +Why this needs the debug build: it needs an interpreter configured with --with-pydebug, and it has to come out of the same image pipeline as the release half or the two sets of timings cannot be compared. + +## The program + +```python +"""What has already happened before your first line runs, and what it cost. + +Nothing here is about your code. Every number is the interpreter getting itself ready: reading a +configuration, building a runtime, creating the main interpreter, importing the modules it cannot +run without, working out where the standard library is, and importing site. + +The module counts come from a child process asking itself what is in sys.modules before it does +anything. The timings are the fastest of twenty runs of a child that does nothing at all, so what +is being measured is startup and only startup. + +Running this on a debug build as well as a release one is the point. The work is identical, the +counts come out the same, and the clock does not, which puts a number on what the assertions and +the reference count bookkeeping cost before your program even begins. +""" + +import subprocess +import sys +import time + +COUNT = """ +import sys +kinds = {"built-in": 0, "frozen": 0, "from a file": 0} +for module in sys.modules.values(): + origin = getattr(getattr(module, "__spec__", None), "origin", None) + if origin in ("built-in", "frozen"): + kinds[origin] += 1 + elif origin is not None: + kinds["from a file"] += 1 +print(len(sys.modules), kinds["built-in"], kinds["frozen"], kinds["from a file"]) +""" + +WAYS = [ + ("everything", []), + ("no site", ["-S"]), + ("isolated and no site", ["-I", "-S"]), +] + + +def child(flags, code): + """Run a fresh interpreter with those flags and hand back what it printed.""" + done = subprocess.run( + [sys.executable, *flags, "-c", code], capture_output=True, text=True, check=True + ) + return done.stdout.strip() + + +def best(flags, rounds=20): + """The fastest of a few runs, which is the honest number on a shared machine.""" + fastest = None + for _ in range(rounds): + started = time.perf_counter() + subprocess.run([sys.executable, *flags, "-c", "pass"], capture_output=True, check=True) + taken = time.perf_counter() - started + fastest = taken if fastest is None else min(fastest, taken) + return fastest + + +def import_cost(): + """Add up the self times that -X importtime prints, in milliseconds.""" + done = subprocess.run( + [sys.executable, "-X", "importtime", "-c", "pass"], capture_output=True, text=True + ) + total = 0 + for line in done.stderr.splitlines(): + parts = line.split("|") + if len(parts) == 3 and parts[0].startswith("import time:"): + head = parts[0].removeprefix("import time:").strip() + if head.isdigit(): + total += int(head) + return total / 1000 + + +print(f"~ this is a debug build: {hasattr(sys, 'gettotalrefcount')}") + +for label, flags in WAYS: + total, builtin, frozen, files = child(flags, COUNT).split() + print(f"modules at the first line, {label}: {total}") + print(f" of those, built into the binary: {builtin}") + print(f" of those, frozen bytecode: {frozen}") + print(f" of those, read from a file on disk: {files}") + +for label, flags in WAYS: + print(f"~ starting up, {label}: {best(flags) * 1000:.1f} ms") + +print(f"~ of that, spent importing: {import_cost():.1f} ms") +``` + +## What it printed + +```text +~ this is a debug build: True +modules at the first line, everything: 33 + of those, built into the binary: 15 + of those, frozen bytecode: 3 + of those, read from a file on disk: 14 +modules at the first line, no site: 21 + of those, built into the binary: 12 + of those, frozen bytecode: 3 + of those, read from a file on disk: 5 +modules at the first line, isolated and no site: 21 + of those, built into the binary: 12 + of those, frozen bytecode: 3 + of those, read from a file on disk: 5 +~ starting up, everything: 56.5 ms +~ starting up, no site: 35.7 ms +~ starting up, isolated and no site: 33.9 ms +~ of that, spent importing: 30.8 ms +``` diff --git a/experiments/tier1/r01-what-startup-costs.md b/experiments/tier1/r01-what-startup-costs.md new file mode 100644 index 0000000..b91996b --- /dev/null +++ b/experiments/tier1/r01-what-startup-costs.md @@ -0,0 +1,125 @@ +# What the interpreter has already done before your first line, on a release build + +Generated by `just build-tier1`. Do not edit by hand, the change will be overwritten. + +How much is already loaded before your program starts, and how long did that take? + +- Lesson: R01 +- Build: release +- Image: ghcr.io/tamnd/cpython-internals/cpython:release@sha256:fb55d6afcf053c974de6447fafbd2be6af20cdb9f596e25a0445607b8af981e3 +- Interpreter: 3.15.0rc1 (37e98da:37e98da, Aug 29 2026, 09:24:54) [GCC 14.2.0] +- Recorded: 2026-09-06 + +Why this needs the release build: it starts twenty child interpreters and takes the fastest, so it needs a machine that is not doing anything else and an interpreter installed the ordinary way rather than a source tree, because the path search is part of what is being timed. + +## The program + +```python +"""What has already happened before your first line runs, and what it cost. + +Nothing here is about your code. Every number is the interpreter getting itself ready: reading a +configuration, building a runtime, creating the main interpreter, importing the modules it cannot +run without, working out where the standard library is, and importing site. + +The module counts come from a child process asking itself what is in sys.modules before it does +anything. The timings are the fastest of twenty runs of a child that does nothing at all, so what +is being measured is startup and only startup. + +Running this on a debug build as well as a release one is the point. The work is identical, the +counts come out the same, and the clock does not, which puts a number on what the assertions and +the reference count bookkeeping cost before your program even begins. +""" + +import subprocess +import sys +import time + +COUNT = """ +import sys +kinds = {"built-in": 0, "frozen": 0, "from a file": 0} +for module in sys.modules.values(): + origin = getattr(getattr(module, "__spec__", None), "origin", None) + if origin in ("built-in", "frozen"): + kinds[origin] += 1 + elif origin is not None: + kinds["from a file"] += 1 +print(len(sys.modules), kinds["built-in"], kinds["frozen"], kinds["from a file"]) +""" + +WAYS = [ + ("everything", []), + ("no site", ["-S"]), + ("isolated and no site", ["-I", "-S"]), +] + + +def child(flags, code): + """Run a fresh interpreter with those flags and hand back what it printed.""" + done = subprocess.run( + [sys.executable, *flags, "-c", code], capture_output=True, text=True, check=True + ) + return done.stdout.strip() + + +def best(flags, rounds=20): + """The fastest of a few runs, which is the honest number on a shared machine.""" + fastest = None + for _ in range(rounds): + started = time.perf_counter() + subprocess.run([sys.executable, *flags, "-c", "pass"], capture_output=True, check=True) + taken = time.perf_counter() - started + fastest = taken if fastest is None else min(fastest, taken) + return fastest + + +def import_cost(): + """Add up the self times that -X importtime prints, in milliseconds.""" + done = subprocess.run( + [sys.executable, "-X", "importtime", "-c", "pass"], capture_output=True, text=True + ) + total = 0 + for line in done.stderr.splitlines(): + parts = line.split("|") + if len(parts) == 3 and parts[0].startswith("import time:"): + head = parts[0].removeprefix("import time:").strip() + if head.isdigit(): + total += int(head) + return total / 1000 + + +print(f"~ this is a debug build: {hasattr(sys, 'gettotalrefcount')}") + +for label, flags in WAYS: + total, builtin, frozen, files = child(flags, COUNT).split() + print(f"modules at the first line, {label}: {total}") + print(f" of those, built into the binary: {builtin}") + print(f" of those, frozen bytecode: {frozen}") + print(f" of those, read from a file on disk: {files}") + +for label, flags in WAYS: + print(f"~ starting up, {label}: {best(flags) * 1000:.1f} ms") + +print(f"~ of that, spent importing: {import_cost():.1f} ms") +``` + +## What it printed + +```text +~ this is a debug build: False +modules at the first line, everything: 33 + of those, built into the binary: 15 + of those, frozen bytecode: 17 + of those, read from a file on disk: 0 +modules at the first line, no site: 21 + of those, built into the binary: 12 + of those, frozen bytecode: 8 + of those, read from a file on disk: 0 +modules at the first line, isolated and no site: 21 + of those, built into the binary: 12 + of those, frozen bytecode: 8 + of those, read from a file on disk: 0 +~ starting up, everything: 26.5 ms +~ starting up, no site: 20.1 ms +~ starting up, isolated and no site: 21.0 ms +~ of that, spent importing: 11.6 ms +``` diff --git a/lessons/CLAIMS.md b/lessons/CLAIMS.md index 13b7524..a56fb4e 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. -557 claims across 71 lessons, 53 of them not observable from Python. +564 claims across 72 lessons, 54 of them not observable from Python. ## B01. Building CPython, and whether you need to @@ -712,6 +712,18 @@ Without it the exception becomes the rule and this goes back to being a book. | if the finalizers in a cycle store self, that collection frees nothing, and a later one frees the objects without calling any finalizer again | [`o14-18`](o14-the-last-thing-an-object-does/o14.ipynb) | | dropping a half consumed generator runs its finally block, and weakref.finalize gives the same effect for an ordinary object without defining __del__ | [`o14-20`](o14-the-last-thing-an-object-does/o14.ipynb) | +## R01. Before your first line + +| Claim | Proved by | +| --- | --- | +| A fresh interpreter has a few dozen modules in sys.modules before it runs any of your code, and with site turned off not one of them was read from a file on disk | [`r01-07`](r01-before-your-first-line/r01.ipynb) | +| The imports that happen before your first line add up to a few milliseconds, and the ones that show up include the import machinery, the codecs, os and site | [`r01-10`](r01-before-your-first-line/r01.ipynb) | +| When the command line and the environment disagree about the optimization level, neither wins by being nearer, the higher of the two is what you get, and -E removes the environment from the argument entirely | [`r01-13`](r01-before-your-first-line/r01.ipynb) | +| sys.path contains an entry for a zip file next to the standard library, and on an ordinary install that file is not there | [`r01-15`](r01-before-your-first-line/r01.ipynb) | +| With -P the entry that would have gone on the front of sys.path is never added at all, so sys.path[0] becomes the first of the standard library entries instead | [`r01-18`](r01-before-your-first-line/r01.ipynb) | +| Starting an interpreter that runs none of your code takes tens of milliseconds, and -S takes a noticeable slice off that because site is a real import | [`r01-21`](r01-before-your-first-line/r01.ipynb) | +| A debug build starts in about twice the time of a release build, and most of the extra is not the assertions, it is the fourteen modules it reads off the disk because a debug build turns frozen modules off | not observable from Python: it is a comparison between two builds of the same source, and one interpreter cannot be both of them | + ## T01. One line, seven stages | Claim | Proved by | diff --git a/lessons/README.md b/lessons/README.md index b44594c..74cc292 100644 --- a/lessons/README.md +++ b/lessons/README.md @@ -75,6 +75,7 @@ Each lesson is a notebook you can run. There is nothing to install and nothing t | [C06. Four threads reading the same list](c06-reading-without-a-lock/c06.ipynb) | Four threads and half a million subscripts each against one shared list, dict and set, coming out at about one thread's worth of work on a build with the GIL and around three times that on a build without it for all three containers, the same reads over a list of small integers against a list of plain object() instances differing by nothing on a build with the GIL and by 3.42 against 0.34 on a build without it, seven values asked whether they have a reference count at all with None and the int 5 having none on either build and the literal 1025 and a long literal string losing theirs only on the free threaded build, four readers dropping from three times one thread to roughly one when a single writer starts scribbling on the list they are reading, and a reader pulling two hundred thousand objects out of a slot while another thread replaces them without once getting back something that had already been freed | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/c06-reading-without-a-lock/c06.ipynb) | | [C07. The lock that comes back](c07-the-lock-that-comes-back/c07.ipynb) | The build asked whether it was compiled without the lock and then asked whether the lock is on this instant, which are different questions with different answers, five child interpreters started with -X gil=1, -X gil=0, PYTHON_GIL=1 and PYTHON_GIL=0 with an ordinary build refusing two of them outright with a fatal error, the smallest extension module that will compile built by hand and imported so you can watch a build that has the lock shrug and a build without it print one warning and flip sys._is_gil_enabled() to True for good, a count of how much of a fresh interpreter is built into the binary against how much is a separate library that would have to declare itself, and eight ordinary single threaded workloads timed on both builds where list appends and f-strings come out faster without the lock and everything else comes out slower | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/c07-the-lock-that-comes-back/c07.ipynb) | | [C08. Sending work to another interpreter](c08-sending-work-to-another-interpreter/c08.ipynb) | Four kinds of callable handed to Interpreter.call so you can watch a plain function and a lambda go across while a function reading a global and a closure are both refused for the same reason, seven values checked three ways at once against is_shareable, pickle and a real queue so the middle route shows up as the row where both of the answers you expected say no and the value crosses anyway, an exception raised in another interpreter caught here and taken apart to show it is a snapshot rather than the exception, six standard library modules imported into a subinterpreter where readline and _tkinter are refused by name, twenty thousand queue round trips timed for three payloads, and two jobs run on a four worker InterpreterPoolExecutor where one comes out faster and the other comes out twelve times slower in the same cell | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/c08-sending-work-to-another-interpreter/c08.ipynb) | +| [R01. Before your first line](r01-before-your-first-line/r01.ipynb) | A child interpreter counting its own sys.modules and splitting them into built into the binary, frozen into the binary and read from a file, with and without site, -X importtime adding up every import that happens before your first line, seven children arguing about sys.flags.optimize to show that the environment and the command line settle it by taking the higher number rather than the nearer one, the whole path configuration printed with an exists check next to every entry so the zip file that is not there stands out, sys.path[0] compared across -c, a script, -m and -P, and ten timed starts of an interpreter that runs nothing at all | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/r01-before-your-first-line/r01.ipynb) | ## The three programs diff --git a/lessons/r01-before-your-first-line/build.py b/lessons/r01-before-your-first-line/build.py new file mode 100644 index 0000000..6fc6065 --- /dev/null +++ b/lessons/r01-before-your-first-line/build.py @@ -0,0 +1,454 @@ +#!/usr/bin/env python +"""R01. Before your first line. + +The first of the runtime lessons. Everything up to here has been about what happens when your +code runs. This one is about everything that has already happened by the time it starts: the +configuration, the two halves of startup, the modules that are in the binary rather than on the +disk, the Python program that works out where the standard library is, and the one entry that +gets pushed onto the front of the search path after all of that is finished. + +Every measurement comes from a child interpreter, because a notebook is far too late to the +party to be a fair witness about its own startup. + +The two Tier 1 recordings are the first release against debug pair in the book, and they turn up +something better than an assertion tax: a debug build imports fourteen modules off the disk that +a release build never touches, because it turns frozen modules off. + +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 +from tier1 import show as recording + +lesson = Lesson("r01-before-your-first-line", "r01") +badge = lesson.badge +cite = lesson.cite +term = lesson.term +figure = Diagrams("r01-before-your-first-line").figure + +ON_RELEASE = "r01-what-startup-costs" +ON_DEBUG = "r01-what-startup-costs-on-a-debug-build" + + +lesson.md(f""" +# R01. Before your first line + +{badge} + +By the time your first statement runs, a lot has already happened. The interpreter has read a configuration out of three different places, built a runtime, made an interpreter, imported a few dozen modules without touching the disk once, worked out where the standard library lives by running a Python program that is not on `sys.path`, and then decided what to put at the front of `sys.path` afterwards. + +None of that is your code, and all of it is on your clock. This lesson goes through it in the order it happens. + +{figure("before-your-first-line", "a flow from the shell running python through configuration, runtime, core startup and main startup to your first line")} +""") + + +lesson.md(""" +## About the source references + +Now and then this lesson points at CPython's own source, like this: `Modules/getpath.c:855-894@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 + +Almost every cell here starts a fresh child interpreter and asks it a question, because the only honest way to look at startup is to watch somebody else do it. Your notebook finished starting up long ago, and it has since imported hundreds of modules that have nothing to do with the subject. + +Some runtimes cannot start a process at all. A browser tab is the usual example. Each cell checks first and says so rather than failing, so the notebook still reads through on a runtime that cannot run it. + +## Which interpreter is this +""") + + +lesson.code( + """ +import pyxray + +pyxray.show() +""", + differs=BANNER, + quiet=True, +) + + +lesson.md(f""" +## What is already there + +Start with the inventory. A brand new interpreter that has been asked to run nothing at all still has a populated `sys.modules`, and the interesting question is not how many but where they came from. + +Every module carries a `__spec__` with an `origin` on it. Three answers matter. `built-in` means the module is C code compiled straight into the `python` binary. `frozen` means it is Python source that was compiled to bytecode when CPython was built, and that bytecode is also inside the binary. Anything else is a path, which means somebody opened a file. + +{lesson.claim("A fresh interpreter has a few dozen modules in sys.modules before it runs any of your code, and with site turned off not one of them was read from a file on disk")} +""") + + +lesson.code( + """ +import subprocess + +COUNT = \"\"\" +import sys +kinds = {"built-in": 0, "frozen": 0, "from a file": 0} +named = [] +for name, module in sys.modules.items(): + origin = getattr(getattr(module, "__spec__", None), "origin", None) + if origin in ("built-in", "frozen"): + kinds[origin] += 1 + elif origin is not None: + kinds["from a file"] += 1 + named.append(name) +print(len(sys.modules), kinds["built-in"], kinds["frozen"], kinds["from a file"]) +print(" ".join(named) or "none at all") +\"\"\" + + +def child(flags, code): + \"\"\"Start a brand new interpreter with those flags and hand back what it printed.\"\"\" + done = subprocess.run( + [sys.executable, *flags, "-c", code], capture_output=True, text=True, check=True + ) + return done.stdout.rstrip() + + +def children_work(): + \"\"\"Some runtimes cannot start a process at all. A browser tab is one of them.\"\"\" + try: + child([], "pass") + except Exception: + return False + return True + + +CHILDREN = children_work() +NO_CHILDREN = " this runtime cannot start another interpreter, so there is nothing to look at" + +if not CHILDREN: + print(NO_CHILDREN) +else: + for what, flags in [("a normal start", []), ("started with -S", ["-S"])]: + counts, names = child(flags, COUNT).splitlines() + total, builtin, frozen, files = counts.split() + print(f" {what:18} {total:>3} modules in sys.modules", end="") + print(f": {builtin} built in, {frozen} frozen, {files} from a file") + print(f" {'':18} the ones from a file: {names}") +""", + varies=( + "the counts move a little between releases and between builds, and a debug build reports " + "far fewer frozen modules for a reason the last section of this lesson gets to" + ), +) + + +lesson.md(f""" +{figure("what-is-already-imported", "a table of how each startup module got there, counted for a normal start and for a start with -S")} + +Look at the `-S` row first, because that is the interpreter on its own. Nothing came off the disk. That is the whole point of freezing: the interpreter cannot import `os` from a file until it knows where files are, and it cannot know where files are until it has imported `os`. Putting the bytecode in the binary breaks the circle. + +The difference between the two rows is `site`, the module that adds `site-packages` to your path and runs any `.pth` files it finds. If your first row listed some modules as coming from a file, those are the ones `site` brought in for you: a virtual environment hook, a `sitecustomize`, whatever your installation puts there. They are yours, not the interpreter's. `site` is not free either, and it is the first thing to turn off if you are starting a lot of short lived interpreters. + +## Two halves, and why there are two + +The `python` binary lands in `pymain_main`, which does two things and no more: get ready, then run {cite("Modules/main.c:855-868@v3.15.0rc1#pymain_main")}. Getting ready means turning your command line and your environment into a `PyConfig` and handing it over {cite("Modules/main.c:37-76@v3.15.0rc1#pymain_init")}. Running means `Py_RunMain`, which works out whether you gave it a script, a module, a string or nothing at all, does that, and then shuts the interpreter down again {cite("Modules/main.c:831-852@v3.15.0rc1#Py_RunMain")}. + +The getting ready part is where the work is, and it comes in two halves {cite("Python/pylifecycle.c:1609-1639@v3.15.0rc1#Py_InitializeFromConfig")}. This is the {term("two phase initialisation")} that the C API documentation keeps mentioning. Core startup builds the runtime, the main interpreter and the built in types, and it has to manage all of that with no import system, no `sys.path` and no codecs {cite("Python/pylifecycle.c:1150-1172@v3.15.0rc1#pyinit_core")}. Main startup is everything that needs those, which includes working out `sys.path` in the first place {cite("Python/pylifecycle.c:1589-1606@v3.15.0rc1#pyinit_main")}. + +That split explains something you have probably seen. A bad configuration comes out as a fatal error with a plain C string rather than as a Python traceback, because at the moment the problem is noticed there is nothing to raise and nothing to print it with. + +You can watch the second half happen. `-X importtime` prints one line per import with the time it took, and for an interpreter running no code at all, every line it prints is startup. + +{lesson.claim("The imports that happen before your first line add up to a few milliseconds, and the ones that show up include the import machinery, the codecs, os and site")} +""") + + +lesson.code( + """ +if not CHILDREN: + print(NO_CHILDREN) +else: + done = subprocess.run( + [sys.executable, "-X", "importtime", "-c", "pass"], capture_output=True, text=True + ) + total = 0 + for line in done.stderr.splitlines(): + parts = line.split("|") + if len(parts) != 3: + continue + head = parts[0].removeprefix("import time:").strip() + if not head.isdigit(): + continue + total += int(head) + if int(head) > 300: + print(f" {parts[2].strip():30} {int(head) / 1000:5.1f} ms on its own") + print(f" {'all of them, added up':30} {total / 1000:5.1f} ms") +""", + varies=( + "which imports clear the threshold depends on your disk and your machine, and an install " + "with a sitecustomize module of its own will show that too" + ), +) + + +lesson.md(f""" +The third column is the module, and the number is its own time rather than its children's. `site` looks cheap on that line and is not cheap overall, because most of what it costs is the imports it triggers underneath. + +## The configuration comes from three places + +A setting can be asked for on the command line, in the environment, or by whoever embedded the interpreter and filled in the `PyConfig` struct directly. When two of them disagree, the natural guess is that the nearest one wins. The guess is wrong. + +The helper that reads a flag out of the environment is a few lines long, and the line that matters is `if (*flag < value) *flag = value;` {cite("Python/preconfig.c:560-578@v3.15.0rc1#_Py_get_env_flag")}. It raises a floor. It never lowers anything. `PYTHONOPTIMIZE` goes through the same shape of code {cite("Python/initconfig.c:1995-2020@v3.15.0rc1#PYTHONOPTIMIZE")}, and on the command line `-O` increments the level rather than assigning it, which is what makes `-OO` mean two {cite("Python/initconfig.c:3210-3222@v3.15.0rc1#optimization_level")}. + +Put those together and the rule is simple to say and easy to forget. The highest number anybody asked for is the one you get. There is one way out, which is `-E`: it throws the environment away before any of this happens, so it does not beat the environment, it deletes it. + +{lesson.claim("When the command line and the environment disagree about the optimization level, neither wins by being nearer, the higher of the two is what you get, and -E removes the environment from the argument entirely")} +""") + + +lesson.code(""" +import os + +ASK = "import sys; print(sys.flags.optimize)" +BASE = {k: v for k, v in os.environ.items() if k != "PYTHONOPTIMIZE"} + +if not CHILDREN: + print(NO_CHILDREN) +else: + for what, flags, extra in [ + ("nothing at all", [], {}), + ("-O", ["-O"], {}), + ("-OO", ["-OO"], {}), + ("PYTHONOPTIMIZE=2", [], {"PYTHONOPTIMIZE": "2"}), + ("PYTHONOPTIMIZE=2 and -O", ["-O"], {"PYTHONOPTIMIZE": "2"}), + ("PYTHONOPTIMIZE=1 and -OO", ["-OO"], {"PYTHONOPTIMIZE": "1"}), + ("PYTHONOPTIMIZE=2 and -E", ["-E"], {"PYTHONOPTIMIZE": "2"}), + ]: + done = subprocess.run( + [sys.executable, *flags, "-c", ASK], + capture_output=True, + text=True, + env={**BASE, **extra}, + ) + print(f" {what:26} sys.flags.optimize is {done.stdout.strip()}") +""") + + +lesson.md(f""" +{figure("the-highest-setting-wins", "a table of what you asked for, what you might expect sys.flags.optimize to be, and what it actually is")} + +The two middle rows are the ones worth remembering. A `PYTHONOPTIMIZE=2` left in a shell profile years ago is not overridden by putting `-O` on the command line today, and nothing tells you. + +## sys.path is worked out by a Python program that is not on sys.path + +Here is the part that surprises people. `sys.path` is not computed in C. It is computed by a Python program, `Modules/getpath.py`, which is compiled to bytecode when CPython itself is built and stored in the binary as a {term("marshal")}led blob {cite("Modules/getpath.c:830-840@v3.15.0rc1#_Py_Get_Getpath_CodeObject")}. During main startup that blob is turned back into a code object and evaluated {cite("Modules/getpath.c:855-894@v3.15.0rc1#_PyConfig_InitPathConfig")}, and what comes out is the whole {term("path configuration")}: `sys.prefix`, `sys.exec_prefix`, `sys.executable`, `sys._base_executable` and `sys.path`. + +It cannot import anything, for the obvious reason that `sys.path` is what it is there to produce. So it is handed eleven C functions to stand in for the `os.path` it cannot have: `abspath`, `basename`, `dirname`, `hassuffix`, `isabs`, `isdir`, `isfile`, `isxfile`, `joinpath`, `readlines` and `realpath` {cite("Modules/getpath.c:560-582@v3.15.0rc1#getpath_methods")}. That list is the entire vocabulary the path search has to work with. + +{figure("who-works-out-sys-path", "a flow from Modules getpath dot py through compilation and marshalling to the values it produces at startup")} + +The output has one entry in it that people misread constantly. There is always a `pythonXY.zip` next to the standard library, and on a normal install that file does not exist. It is not a bug and it is not left over from anything. Putting a zip file there is a supported way to ship a whole standard library in one file, so the entry is added unconditionally and the import system simply finds nothing when it looks. + +{lesson.claim("sys.path contains an entry for a zip file next to the standard library, and on an ordinary install that file is not there")} +""") + + +lesson.code( + """ +WHERE = \"\"\" +import os +import sys +print(" sys.executable ", sys.executable) +print(" sys._base_executable ", sys._base_executable) +print(" sys.prefix ", sys.prefix) +print(" sys.base_prefix ", sys.base_prefix) +for entry in sys.path: + shown = entry or "(the empty string)" + print(" sys.path ", shown, "exists:", os.path.exists(entry)) +\"\"\" + +if not CHILDREN: + print(NO_CHILDREN) +else: + print(child([], WHERE)) +""", + varies=( + "every path here is where your Python happens to be installed, and inside a virtual " + "environment sys.prefix and sys.base_prefix point at different places" + ), +) + + +lesson.md(f""" +If you ran that inside a virtual environment, `sys.prefix` and `sys.base_prefix` came out different. That difference is the whole mechanism: a virtual environment is a `pyvenv.cfg` file that tells `getpath.py` where the real installation is, and everything else follows from the two prefixes. + +## The front of sys.path is decided last + +Everything above happens while the interpreter is starting. The front of `sys.path` does not. It is pushed on afterwards, once the interpreter is already running, by the code that is about to hand control to your program {cite("Modules/main.c:657-696@v3.15.0rc1#pymain_run_python")}. + +What goes in depends on how you started. The directory the script is in, for a script. The current directory, for `-m`. The empty string, for `-c` and for the interactive prompt, which the import system reads as the current directory at the time of each import. + +This is the cause of the most common confusion in Python. A file called `random.py` sitting next to your script shadows the standard library module of the same name, and it does so because its directory was put in front of the standard library after the standard library had already been located. + +There is a switch for it. `-P` on the command line, or `PYTHONSAFEPATH` in the environment, is the {term("safe path")} option {cite("Modules/main.c:696-728@v3.15.0rc1#safe_path")}. Note what it does: the entry is never added, rather than added and then taken away. So `sys.path[0]` becomes whatever was going to be first anyway, which is that zip file from the previous section. + +{lesson.claim("With -P the entry that would have gone on the front of sys.path is never added at all, so sys.path[0] becomes the first of the standard library entries instead")} +""") + + +lesson.code( + """ +import pathlib +import tempfile + +SHOW = "import sys; print(repr(sys.path[0]))" + +if not CHILDREN: + print(NO_CHILDREN) +else: + with tempfile.TemporaryDirectory() as folder: + (pathlib.Path(folder) / "show.py").write_text(SHOW) + script = str(pathlib.Path(folder) / "show.py") + for what, args, where in [ + ("python -c code", ["-c", SHOW], None), + ("python show.py", [script], None), + ("python -m show", ["-m", "show"], folder), + ("python -P show.py", ["-P", script], None), + ]: + done = subprocess.run( + [sys.executable, *args], capture_output=True, text=True, cwd=where + ) + print(f" {what:22} {done.stdout.strip()}") + print(f" the script is in {folder!r}") +""", + varies=( + "the temporary directory is different every run, and on macOS the child reports it with " + "the symbolic links resolved so it looks slightly different from the last line" + ), +) + + +lesson.md(f""" +{figure("what-goes-in-front", "a table of how you started python, what lands in sys path index zero, and when it was added")} + +The last row is the one to reach for when something is being shadowed and you cannot work out what. If the problem goes away under `-P`, a file in your own directory was the cause. + +## What all of it costs + +Now the clock. The cell below starts an interpreter that runs nothing, ten times, and keeps the fastest, because the fastest run is the one where the machine was not busy doing something else. + +{lesson.claim("Starting an interpreter that runs none of your code takes tens of milliseconds, and -S takes a noticeable slice off that because site is a real import")} +""") + + +lesson.code( + """ +import time + + +def best(flags, rounds=10): + \"\"\"The fastest of a few runs, which is the honest number on a machine you share.\"\"\" + fastest = None + for _ in range(rounds): + started = time.perf_counter() + subprocess.run([sys.executable, *flags, "-c", "pass"], capture_output=True, check=True) + taken = time.perf_counter() - started + fastest = taken if fastest is None else min(fastest, taken) + return fastest + + +if not CHILDREN: + print(NO_CHILDREN) +else: + for what, flags in [ + ("everything", []), + ("no site, with -S", ["-S"]), + ("no site and no environment", ["-I", "-S"]), + ]: + print(f" {what:28} {best(flags) * 1000:5.1f} ms") +""", + varies=( + "startup time depends on your disk, your machine and whether the files are still in the " + "operating system cache, so only the gaps between the three rows mean anything" + ), +) + + +lesson.md(f""" +Those numbers include the cost of your machine starting a process at all, which is not the interpreter's fault, so treat the differences between the rows as the real content. + +The two recordings below run the same program in two containers built from the same source. One is an ordinary release build. The other was configured with `--with-pydebug`, which is the build you would use if you were working on CPython itself. + +{recording(ON_RELEASE)} + +{recording(ON_DEBUG)} + +{figure("what-startup-costs", "bars of milliseconds to start and do nothing, for a release build and a debug build, each with and without site")} + +The debug build takes 56.5 ms against 26.5 ms. The obvious explanation is the assertions and the reference count bookkeeping, and the obvious explanation is mostly wrong. Look at the module counts instead. The release build reports 17 frozen and 0 from a file. The debug build reports 3 frozen and 14 from a file. That is not the same work done more slowly, it is different work. + +A debug build turns frozen modules off, and it is one `#ifdef` in the defaults {cite("Python/initconfig.c:1193-1201@v3.15.0rc1#use_frozen_modules")}. The reasoning is good: if you are debugging CPython you want to step through the real `Lib/os.py` on disk, not through bytecode that was baked into the binary at build time. The price is that `os`, `site`, `codecs` and everything else frozen now comes off the disk, and the import bill goes from 11.6 ms to 30.8 ms. You can put it back with `-X frozen_modules=on`, or with `PYTHON_FROZEN_MODULES=on` in the environment {cite("Python/initconfig.c:2849-2866@v3.15.0rc1#frozen_modules")}. + +{lesson.claim("A debug build starts in about twice the time of a release build, and most of the extra is not the assertions, it is the fourteen modules it reads off the disk because a debug build turns frozen modules off", unobservable="it is a comparison between two builds of the same source, and one interpreter cannot be both of them")} +""") + + +lesson.md(""" +## Try it yourself + +Four things, in rough order of how much you will learn. + +Make a file called `random.py` in a directory, put `print("not the real one")` in it, put an empty `main.py` next to it that does `import random`, and run it. Then run it again with `-P`. That is the shadowing problem and its fix in about thirty seconds. + +Change the threshold in the import time cell from 300 down to 0 and read the whole list. It is not long, and it is the complete set of things that have to exist before Python can run a single line. Look for `zipimport`, which is there so that the zip file entry from earlier can actually be used. + +Add `("-X frozen_modules=off", ["-X", "frozen_modules=off"])` to the counting cell and watch the frozen count collapse and the from a file count rise. That is you turning a release build into a debug build for one setting, and it is worth doing before reading the last section again. + +Set `PYTHONOPTIMIZE=2` in your shell and then try to get back to zero using only command line flags. There is exactly one way and it is not `-O`. + +## What you now know + +A fresh interpreter has a few dozen modules loaded before your first statement, and on a release build none of them were read from a file. They are either C compiled into the binary or Python bytecode frozen into it. + +Startup is two halves on purpose. Core startup runs with no import system and no `sys.path`, which is why configuration errors come out as fatal errors rather than exceptions. Main startup is everything that needs an import system, including working out `sys.path`. + +Settings can come from the command line, the environment or an embedder, and the answer is the highest number anybody asked for rather than whichever one is nearest. `-E` is the escape hatch, and it works by discarding the environment rather than by outranking it. + +`sys.path` is produced by a Python program that is frozen into the binary and handed eleven C functions to stand in for `os.path`, because the module it would normally use cannot be imported until it has finished. Its output always includes a zip file that usually does not exist. + +The front of `sys.path` is added last, after startup is over, and it is the directory of your script or the current directory. That is the whole mechanism behind accidentally shadowing a standard library module, and `-P` turns it off. + +A plain start costs tens of milliseconds, of which `site` is a real slice, and a debug build costs about double, mostly because it reads from disk what a release build reads from its own binary. + +## What is next + +This lesson kept saying "the runtime" and "the interpreter" as if they were one thing. They are not. There is a runtime, there are interpreters inside it, and there are threads inside those, and each of the three owns a different pile of state. C04 made a second interpreter and never said what one is made of. + +R02 opens that up. Where every piece of the state this book has been poking at for seventy lessons actually lives, which of those piles is shared and which is not, and why moving one field from one struct to another is a change worth arguing about. +""") + + +raise SystemExit(lesson.save()) diff --git a/lessons/r01-before-your-first-line/diagrams.py b/lessons/r01-before-your-first-line/diagrams.py new file mode 100644 index 0000000..2f77887 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +"""The diagrams for R01, everything the interpreter does before your first statement runs. + +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 order follows the lesson. The shape of startup, what is already in sys.modules when you +get there, where the configuration comes from, who works out sys.path, what goes in front of +it, and what the whole thing costs on two builds. +""" + +from nbdiagram import Gallery, figures + +gallery = Gallery("r01-before-your-first-line") + +gallery.add( + figures.flow( + "before-your-first-line", + [ + "the shell runs python", + "read the configuration", + "build the runtime", + "core startup", + "main startup", + "your first line", + ], + title="What happens between the shell and your code", + tones=["input", "quiet", "quiet", "intermediate", "intermediate", "durable"], + labels=[ + "argv and the environment", + "one process wide struct", + "no imports possible yet", + "sys.path, then site", + ], + ) +) + + +gallery.add( + figures.table( + "what-is-already-imported", + ["how the module got there", "normal start", "started with -S"], + [ + ["built into the python binary", "15", "12"], + ["frozen bytecode, also in the binary", "17", "8"], + ["read from a file on disk", "0", "0"], + ["everything in sys.modules", "33", "21"], + ], + title="What is in sys.modules before your first statement", + caption="Not one of them was read from disk. They are all inside the binary already.", + tones=["durable", "focus", "warning", "quiet"], + ) +) + + +gallery.add( + figures.table( + "the-highest-setting-wins", + ["what you asked for", "what you might expect", "what you actually get"], + [ + ["-O on its own", "1", "1"], + ["PYTHONOPTIMIZE=2 on its own", "2", "2"], + ["PYTHONOPTIMIZE=2 and -O", "1, the flag is nearer", "2"], + ["PYTHONOPTIMIZE=1 and -OO", "1, the env is stronger", "2"], + ["PYTHONOPTIMIZE=2 and -E", "2", "0"], + ], + title="sys.flags.optimize when the flag and the environment disagree", + caption="Neither one wins. The higher number wins, unless -E throws the environment out.", + tones=["quiet", "quiet", "focus", "focus", "warning"], + ) +) + + +gallery.add( + figures.flow( + "who-works-out-sys-path", + [ + "Modules/getpath.py", + "compiled at build time", + "a byte blob in the binary", + "run during startup", + "sys.path and friends", + ], + title="sys.path is produced by a Python program that is not on sys.path", + tones=["input", "quiet", "intermediate", "intermediate", "durable"], + labels=[ + "to bytecode", + "marshalled in", + "eleven C helpers stand in for os.path", + ], + ) +) + + +gallery.add( + figures.table( + "what-goes-in-front", + ["how you started it", "what lands in sys.path[0]", "when it was added"], + [ + ["python script.py", "the directory the script is in", "last, after everything"], + ["python -c code", "the empty string", "last, after everything"], + ["python -m module", "the current directory", "last, after everything"], + ["python -P script.py", "nothing is added at all", "never"], + ], + title="The front of sys.path is decided after the rest of it", + caption="This is why a file called random.py next to your script can shadow the real one.", + tones=["focus", "focus", "focus", "durable"], + ) +) + + +gallery.add( + figures.bars( + "what-startup-costs", + [ + ["release, normal", 26.5], + ["release, -S", 20.1], + ["debug, normal", 56.5], + ["debug, -S", 35.7], + ], + unit="ms to start and do nothing", + title="Starting an interpreter that runs no code of yours", + caption="A debug build turns frozen modules off, so it reads from disk what release does not.", + tones=["durable", "durable", "warning", "quiet"], + width=500, + ) +) + + +raise SystemExit(gallery.save()) diff --git a/lessons/r01-before-your-first-line/diagrams/before-your-first-line.excalidraw b/lessons/r01-before-your-first-line/diagrams/before-your-first-line.excalidraw new file mode 100644 index 0000000..8d7ad95 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/before-your-first-line.excalidraw @@ -0,0 +1,912 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "0f2f06de6d4121330526", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 618.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": "What happens between the shell and your code", + "originalText": "What happens between the shell and your code", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "6c549ec9f9b1f57e2620", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 264.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": "ea3007c2269353e7d98a", + "type": "text" + }, + { + "id": "43ce2b45edcb8d55421f", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "ea3007c2269353e7d98a", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 232.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 shell runs python", + "originalText": "the shell runs python", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "6c549ec9f9b1f57e2620", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "a9b5ed1ee9ceb20e898b", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 264.5, + "height": 70.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "ce58ee47d5578c545fdf", + "type": "text" + }, + { + "id": "43ce2b45edcb8d55421f", + "type": "arrow" + }, + { + "id": "249d9c0b40fe3ae9fece", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "ce58ee47d5578c545fdf", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 232.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": "read the configuration", + "originalText": "read the configuration", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "a9b5ed1ee9ceb20e898b", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "7815bb4bb0e6f87d8b87", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 264.5, + "height": 70.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "79cc77e58b49f7f16f2b", + "type": "text" + }, + { + "id": "249d9c0b40fe3ae9fece", + "type": "arrow" + }, + { + "id": "00dd09f0ec350a1f50cf", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "79cc77e58b49f7f16f2b", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 232.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": "build the runtime", + "originalText": "build the runtime", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "7815bb4bb0e6f87d8b87", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "57966e7c9b37238828a3", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 264.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": "c90416bd92a037624490", + "type": "text" + }, + { + "id": "00dd09f0ec350a1f50cf", + "type": "arrow" + }, + { + "id": "d25b9048c34676f2ef82", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "c90416bd92a037624490", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 232.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": "core startup", + "originalText": "core startup", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "57966e7c9b37238828a3", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "bd2c23a654b525f5a4e8", + "type": "rectangle", + "x": 0.0, + "y": 570.0, + "width": 264.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": "f3fcee06ab1bcbd10f49", + "type": "text" + }, + { + "id": "d25b9048c34676f2ef82", + "type": "arrow" + }, + { + "id": "e6e82cff52b9ddebb6b9", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "f3fcee06ab1bcbd10f49", + "type": "text", + "x": 16.0, + "y": 592.5, + "width": 232.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": "main startup", + "originalText": "main startup", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "bd2c23a654b525f5a4e8", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "007af47a1b0015a40069", + "type": "rectangle", + "x": 0.0, + "y": 700.0, + "width": 264.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": "a7450502b1a024dd2da9", + "type": "text" + }, + { + "id": "e6e82cff52b9ddebb6b9", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "a7450502b1a024dd2da9", + "type": "text", + "x": 16.0, + "y": 722.5, + "width": 232.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": "your first line", + "originalText": "your first line", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "007af47a1b0015a40069", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "43ce2b45edcb8d55421f", + "type": "arrow", + "x": 132.25, + "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": "6c549ec9f9b1f57e2620", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "a9b5ed1ee9ceb20e898b", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "c84b321468eecee92526", + "type": "text", + "x": 144.25, + "y": 140.0, + "width": 218.15999999999997, + "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": "argv and the environment", + "originalText": "argv and the environment", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "249d9c0b40fe3ae9fece", + "type": "arrow", + "x": 132.25, + "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": "a9b5ed1ee9ceb20e898b", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "7815bb4bb0e6f87d8b87", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "cac793d4950c4329d353", + "type": "text", + "x": 144.25, + "y": 270.0, + "width": 208.56, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "one process wide struct", + "originalText": "one process wide struct", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "00dd09f0ec350a1f50cf", + "type": "arrow", + "x": 132.25, + "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": "7815bb4bb0e6f87d8b87", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "57966e7c9b37238828a3", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "a1f744e0883c7acdb522", + "type": "text", + "x": 144.25, + "y": 400.0, + "width": 204.95999999999998, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "no imports possible yet", + "originalText": "no imports possible yet", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d25b9048c34676f2ef82", + "type": "arrow", + "x": 132.25, + "y": 510.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "57966e7c9b37238828a3", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "bd2c23a654b525f5a4e8", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "31e68926d53087e9ec2a", + "type": "text", + "x": 144.25, + "y": 530.0, + "width": 160.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": "sys.path, then site", + "originalText": "sys.path, then site", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e6e82cff52b9ddebb6b9", + "type": "arrow", + "x": 132.25, + "y": 640.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "bd2c23a654b525f5a4e8", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "007af47a1b0015a40069", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/r01-before-your-first-line/diagrams/before-your-first-line.svg b/lessons/r01-before-your-first-line/diagrams/before-your-first-line.svg new file mode 100644 index 0000000..00bb640 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/before-your-first-line.svg @@ -0,0 +1 @@ +What happens between the shell and your codethe shell runs pythonread the configurationbuild the runtimecore startupmain startupyour first lineargv and the environmentone process wide structno imports possible yetsys.path, then site diff --git a/lessons/r01-before-your-first-line/diagrams/the-highest-setting-wins.excalidraw b/lessons/r01-before-your-first-line/diagrams/the-highest-setting-wins.excalidraw new file mode 100644 index 0000000..852b449 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/the-highest-setting-wins.excalidraw @@ -0,0 +1,1235 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "ae389e3fbe3367fd2716", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 817.92, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "sys.flags.optimize when the flag and the environment disagree", + "originalText": "sys.flags.optimize when the flag and the environment disagree", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "bd1f80647d6f2e93bd12", + "type": "text", + "x": 14.0, + "y": 50.0, + "width": 167.76, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "what you asked for", + "originalText": "what you asked for", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7b2191f54b0e6a8e3e5f", + "type": "text", + "x": 301.2, + "y": 50.0, + "width": 198.72, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "what you might expect", + "originalText": "what you might expect", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "bf92f68bff524f5adf0e", + "type": "text", + "x": 540.4, + "y": 50.0, + "width": 189.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 you actually get", + "originalText": "what you actually get", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "0075da6097a2c0f50595", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 756.0, + "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 + ], + [ + 756.0, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "bbf6782aea9f6cfa67b2", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 756.0, + "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 + ], + [ + 756.0, + 0.0 + ], + [ + 756.0, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "e2eca02a13df89a9ba31", + "type": "text", + "x": 14.0, + "y": 86.0, + "width": 124.8, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "-O on its own", + "originalText": "-O on its own", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2cc1b1b07a881aecc376", + "type": "text", + "x": 301.2, + "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": "1", + "originalText": "1", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "0a25d703dd0e42ef48ad", + "type": "text", + "x": 540.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": "1", + "originalText": "1", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d9f0a0bcfffa4c842fa0", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 756.0, + "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 + ], + [ + 756.0, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "8e62fbb578eac8a0de8b", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 756.0, + "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 + ], + [ + 756.0, + 0.0 + ], + [ + 756.0, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "745480d295d72603a7b6", + "type": "text", + "x": 14.0, + "y": 126.0, + "width": 259.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": "PYTHONOPTIMIZE=2 on its own", + "originalText": "PYTHONOPTIMIZE=2 on its own", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "1e1a9749632a804c7120", + "type": "text", + "x": 301.2, + "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": "2", + "originalText": "2", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a958c1ff503979fbb493", + "type": "text", + "x": 540.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": "2", + "originalText": "2", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "062b773ad763a7a4a2c2", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 756.0, + "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 + ], + [ + 756.0, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "64cd540cdfc1d63da907", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 756.0, + "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 + ], + [ + 756.0, + 0.0 + ], + [ + 756.0, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "2449d7989ae4c69db9dc", + "type": "text", + "x": 14.0, + "y": 166.0, + "width": 220.79999999999998, + "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": "PYTHONOPTIMIZE=2 and -O", + "originalText": "PYTHONOPTIMIZE=2 and -O", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "910ad6b5610d672ecebb", + "type": "text", + "x": 301.2, + "y": 166.0, + "width": 201.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "1, the flag is nearer", + "originalText": "1, the flag is nearer", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "4911d114c9df7284e104", + "type": "text", + "x": 540.4, + "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": "2", + "originalText": "2", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "138918bc5ee2341f919e", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 756.0, + "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 + ], + [ + 756.0, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "cc1700d9685603484c02", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 756.0, + "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 + ], + [ + 756.0, + 0.0 + ], + [ + 756.0, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "7598cdf641f71dd1722b", + "type": "text", + "x": 14.0, + "y": 206.0, + "width": 230.39999999999998, + "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": "PYTHONOPTIMIZE=1 and -OO", + "originalText": "PYTHONOPTIMIZE=1 and -OO", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d197bd5cb3c9853a3e9f", + "type": "text", + "x": 301.2, + "y": 206.0, + "width": 211.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": "1, the env is stronger", + "originalText": "1, the env is stronger", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d3a3c9841d1592889845", + "type": "text", + "x": 540.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": "2", + "originalText": "2", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "20f347bb714c720f2c0c", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 756.0, + "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 + ], + [ + 756.0, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "fd015d7e06ddb9c5e5c3", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 756.0, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 756.0, + 0.0 + ], + [ + 756.0, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "16cebd232a3f81deea8f", + "type": "text", + "x": 14.0, + "y": 246.0, + "width": 220.79999999999998, + "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": "PYTHONOPTIMIZE=2 and -E", + "originalText": "PYTHONOPTIMIZE=2 and -E", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d0be0671319dec27b5c4", + "type": "text", + "x": 301.2, + "y": 246.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": "2", + "originalText": "2", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f0109839d6e4b532b716", + "type": "text", + "x": 540.4, + "y": 246.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": "118d83ca43b3b10bdd16", + "type": "line", + "x": 0.0, + "y": 276.0, + "width": 756.0, + "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 + ], + [ + 756.0, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "a58a68c69d3ecef8c8a7", + "type": "text", + "x": 0.0, + "y": 296.0, + "width": 718.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": "Neither one wins. The higher number wins, unless -E throws the environment out.", + "originalText": "Neither one wins. The higher number wins, unless -E throws the environment out.", + "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/r01-before-your-first-line/diagrams/the-highest-setting-wins.svg b/lessons/r01-before-your-first-line/diagrams/the-highest-setting-wins.svg new file mode 100644 index 0000000..46946e2 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/the-highest-setting-wins.svg @@ -0,0 +1 @@ +sys.flags.optimize when the flag and the environment disagreewhat you asked forwhat you might expectwhat you actually get-O on its own11PYTHONOPTIMIZE=2 on its own22PYTHONOPTIMIZE=2 and -O1, the flag is nearer2PYTHONOPTIMIZE=1 and -OO1, the env is stronger2PYTHONOPTIMIZE=2 and -E20Neither one wins. The higher number wins, unless -E throws the environment out. diff --git a/lessons/r01-before-your-first-line/diagrams/what-goes-in-front.excalidraw b/lessons/r01-before-your-first-line/diagrams/what-goes-in-front.excalidraw new file mode 100644 index 0000000..22c9a81 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/what-goes-in-front.excalidraw @@ -0,0 +1,1035 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "d6fd1ee9cbe51f3dc8af", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 671.4, + "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 front of sys.path is decided after the rest of it", + "originalText": "The front of sys.path is decided after the rest of it", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2add490e9113134abfae", + "type": "text", + "x": 14.0, + "y": 50.0, + "width": 160.56, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "how you started it", + "originalText": "how you started it", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d4d302ff61b6124e3525", + "type": "text", + "x": 224.4, + "y": 50.0, + "width": 220.56, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "what lands in sys.path[0]", + "originalText": "what lands in sys.path[0]", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "90fa68a638cd0d910cdc", + "type": "text", + "x": 540.4, + "y": 50.0, + "width": 167.52, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "when it was added", + "originalText": "when it was added", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "383f67bcf770e57e4f07", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 765.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 + ], + [ + 765.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "14c09311a26784219810", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 765.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 + ], + [ + 765.6, + 0.0 + ], + [ + 765.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "05ffd0fac288363dc790", + "type": "text", + "x": 14.0, + "y": 86.0, + "width": 153.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": "python script.py", + "originalText": "python script.py", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "c697698c8bb8d0de0421", + "type": "text", + "x": 224.4, + "y": 86.0, + "width": 288.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": "the directory the script is in", + "originalText": "the directory the script is in", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "308f4a9be1792a4f6a50", + "type": "text", + "x": 540.4, + "y": 86.0, + "width": 211.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": "last, after everything", + "originalText": "last, after everything", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "07c366ea038ad0b1ee65", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 765.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 + ], + [ + 765.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "2a81b31628c4e834a5dc", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 765.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 + ], + [ + 765.6, + 0.0 + ], + [ + 765.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "fb217e3173a2fde57b54", + "type": "text", + "x": 14.0, + "y": 126.0, + "width": 134.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "python -c code", + "originalText": "python -c code", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "04ad013ff7e0760d0962", + "type": "text", + "x": 224.4, + "y": 126.0, + "width": 153.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": "the empty string", + "originalText": "the empty string", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "ba6c8585fe0bbd132782", + "type": "text", + "x": 540.4, + "y": 126.0, + "width": 211.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": "last, after everything", + "originalText": "last, after everything", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "50cd98e7468ea29c0b62", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 765.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 + ], + [ + 765.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "6a5e45fe5361eebd76e6", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 765.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 + ], + [ + 765.6, + 0.0 + ], + [ + 765.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "f1eb1e5d6d24d0059e76", + "type": "text", + "x": 14.0, + "y": 166.0, + "width": 153.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": "python -m module", + "originalText": "python -m module", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d4e4825c1bdc7e86a392", + "type": "text", + "x": 224.4, + "y": 166.0, + "width": 201.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the current directory", + "originalText": "the current directory", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "30fe5dde4202e10fe71b", + "type": "text", + "x": 540.4, + "y": 166.0, + "width": 211.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": "last, after everything", + "originalText": "last, after everything", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "4df2b3ac6b606b199f5c", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 765.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 + ], + [ + 765.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "337604f36c3af3269d39", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 765.6, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "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 + ], + [ + 765.6, + 0.0 + ], + [ + 765.6, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "03ea97a9be31c90aab74", + "type": "text", + "x": 14.0, + "y": 206.0, + "width": 182.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "python -P script.py", + "originalText": "python -P script.py", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "549ba081fbaa8a1eeda8", + "type": "text", + "x": 224.4, + "y": 206.0, + "width": 220.79999999999998, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "nothing is added at all", + "originalText": "nothing is added at all", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "1d543b19c9c3d2b635b6", + "type": "text", + "x": 540.4, + "y": 206.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": "never", + "originalText": "never", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "b9ce9eebbaaff8eb672a", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 765.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 + ], + [ + 765.6, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "669656c89015e687bed8", + "type": "text", + "x": 0.0, + "y": 256.0, + "width": 716.88, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "This is why a file called random.py next to your script can shadow the real one.", + "originalText": "This is why a file called random.py next to your script can shadow the real one.", + "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/r01-before-your-first-line/diagrams/what-goes-in-front.svg b/lessons/r01-before-your-first-line/diagrams/what-goes-in-front.svg new file mode 100644 index 0000000..c315b21 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/what-goes-in-front.svg @@ -0,0 +1 @@ +The front of sys.path is decided after the rest of ithow you started itwhat lands in sys.path[0]when it was addedpython script.pythe directory the script is inlast, after everythingpython -c codethe empty stringlast, after everythingpython -m modulethe current directorylast, after everythingpython -P script.pynothing is added at allneverThis is why a file called random.py next to your script can shadow the real one. diff --git a/lessons/r01-before-your-first-line/diagrams/what-is-already-imported.excalidraw b/lessons/r01-before-your-first-line/diagrams/what-is-already-imported.excalidraw new file mode 100644 index 0000000..d1ed801 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/what-is-already-imported.excalidraw @@ -0,0 +1,1035 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "2127277ffcf37b4da3fc", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 664.92, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "What is in sys.modules before your first statement", + "originalText": "What is in sys.modules before your first statement", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "9b4fe53cfdc439b7304f", + "type": "text", + "x": 14.0, + "y": 50.0, + "width": 223.92, + "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": "how the module got there", + "originalText": "how the module got there", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "42aa2e7f57c77a8f33a9", + "type": "text", + "x": 378.0, + "y": 50.0, + "width": 102.96, + "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": "normal start", + "originalText": "normal start", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "8ae8b95d6da23b64aa43", + "type": "text", + "x": 521.2, + "y": 50.0, + "width": 131.76, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "started with -S", + "originalText": "started with -S", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "455de543b4be14e2e29f", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 679.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 + ], + [ + 679.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "330ecb1541ef4461c5f6", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 679.2, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "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 + ], + [ + 679.2, + 0.0 + ], + [ + 679.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "4d44fde56f648c146ae8", + "type": "text", + "x": 14.0, + "y": 86.0, + "width": 268.8, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "built into the python binary", + "originalText": "built into the python binary", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a335c84e582ac750a022", + "type": "text", + "x": 378.0, + "y": 86.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": "15", + "originalText": "15", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "57c45b3f9fa0bd55ded0", + "type": "text", + "x": 521.2, + "y": 86.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": "12", + "originalText": "12", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "63688b5384beb694225f", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 679.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 + ], + [ + 679.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "192ff2930e63ca764c32", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 679.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 + ], + [ + 679.2, + 0.0 + ], + [ + 679.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "173b52e09455c178ff42", + "type": "text", + "x": 14.0, + "y": 126.0, + "width": 336.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": "frozen bytecode, also in the binary", + "originalText": "frozen bytecode, also in the binary", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "560d82f773366a23000f", + "type": "text", + "x": 378.0, + "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": "17", + "originalText": "17", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "de74ac1814cccd384144", + "type": "text", + "x": 521.2, + "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": "8", + "originalText": "8", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "33d2f6c9fabf4c55acbd", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 679.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 + ], + [ + 679.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "866eae985ba7f43a5138", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 679.2, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 679.2, + 0.0 + ], + [ + 679.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "ee9c4f2093e3c36e968a", + "type": "text", + "x": 14.0, + "y": 166.0, + "width": 230.39999999999998, + "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": "read from a file on disk", + "originalText": "read from a file on disk", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "9bd1f99b57b52285c48e", + "type": "text", + "x": 378.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": "0", + "originalText": "0", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f4e39c943d8165743fe1", + "type": "text", + "x": 521.2, + "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": "0", + "originalText": "0", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a1b8b07f120464213969", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 679.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 + ], + [ + 679.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "8f71b26b651e9faf377e", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 679.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 + ], + [ + 679.2, + 0.0 + ], + [ + 679.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "f0f0d42962810d423c00", + "type": "text", + "x": 14.0, + "y": 206.0, + "width": 240.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": "everything in sys.modules", + "originalText": "everything in sys.modules", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "cf14bd54be64894cbdbc", + "type": "text", + "x": 378.0, + "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": "33", + "originalText": "33", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d241f25a103e2f50111c", + "type": "text", + "x": 521.2, + "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": "21", + "originalText": "21", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7b5ba40f6e837baa130b", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 679.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 + ], + [ + 679.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "e05111e3d0ce98e96104", + "type": "text", + "x": 0.0, + "y": 256.0, + "width": 668.88, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "Not one of them was read from disk. They are all inside the binary already.", + "originalText": "Not one of them was read from disk. They are all inside the binary already.", + "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/r01-before-your-first-line/diagrams/what-is-already-imported.svg b/lessons/r01-before-your-first-line/diagrams/what-is-already-imported.svg new file mode 100644 index 0000000..5926bd1 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/what-is-already-imported.svg @@ -0,0 +1 @@ +What is in sys.modules before your first statementhow the module got therenormal startstarted with -Sbuilt into the python binary1512frozen bytecode, also in the binary178read from a file on disk00everything in sys.modules3321Not one of them was read from disk. They are all inside the binary already. diff --git a/lessons/r01-before-your-first-line/diagrams/what-startup-costs.excalidraw b/lessons/r01-before-your-first-line/diagrams/what-startup-costs.excalidraw new file mode 100644 index 0000000..6c72300 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/what-startup-costs.excalidraw @@ -0,0 +1,586 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "320c356b8df5d10ebb08", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 639.0, + "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": "Starting an interpreter that runs no code of yours", + "originalText": "Starting an interpreter that runs no code of yours", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f19b11ccb04056f7f82d", + "type": "text", + "x": 0.0, + "y": 57.0, + "width": 144.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": "release, normal", + "originalText": "release, normal", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7f6adc06ca3c914b4a1d", + "type": "line", + "x": 164.0, + "y": 50.0, + "width": 234.5132743362832, + "height": 34.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "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 + ], + [ + 234.5132743362832, + 0.0 + ], + [ + 234.5132743362832, + 34.0 + ], + [ + 0.0, + 34.0 + ], + [ + 0.0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "6597443bb849465db974", + "type": "text", + "x": 408.5132743362832, + "y": 57.0, + "width": 297.59999999999997, + "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": "26.5 ms to start and do nothing", + "originalText": "26.5 ms to start and do nothing", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "9e1661577a0a57a9580d", + "type": "text", + "x": 0.0, + "y": 101.0, + "width": 105.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": "release, -S", + "originalText": "release, -S", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "9af5e415f0c8d0a2d164", + "type": "line", + "x": 164.0, + "y": 94.0, + "width": 177.87610619469024, + "height": 34.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "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 + ], + [ + 177.87610619469024, + 0.0 + ], + [ + 177.87610619469024, + 34.0 + ], + [ + 0.0, + 34.0 + ], + [ + 0.0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "40b532b5148694d62986", + "type": "text", + "x": 351.87610619469024, + "y": 101.0, + "width": 297.59999999999997, + "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": "20.1 ms to start and do nothing", + "originalText": "20.1 ms to start and do nothing", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "b8107d029511c7b33417", + "type": "text", + "x": 0.0, + "y": 145.0, + "width": 124.8, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "debug, normal", + "originalText": "debug, normal", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2eda486aebdbc9980ee3", + "type": "line", + "x": 164.0, + "y": 138.0, + "width": 500.0, + "height": 34.0, + "angle": 0, + "strokeColor": "#e03131", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 500.0, + 0.0 + ], + [ + 500.0, + 34.0 + ], + [ + 0.0, + 34.0 + ], + [ + 0.0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "4dd0aa54bb820a42bca0", + "type": "text", + "x": 674.0, + "y": 145.0, + "width": 297.59999999999997, + "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": "56.5 ms to start and do nothing", + "originalText": "56.5 ms to start and do nothing", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "8a93884acc276de2f9d0", + "type": "text", + "x": 0.0, + "y": 189.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": "debug, -S", + "originalText": "debug, -S", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "dd176cddd3a816a9375a", + "type": "line", + "x": 164.0, + "y": 182.0, + "width": 315.929203539823, + "height": 34.0, + "angle": 0, + "strokeColor": "#495057", + "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.0 + ], + [ + 315.929203539823, + 0.0 + ], + [ + 315.929203539823, + 34.0 + ], + [ + 0.0, + 34.0 + ], + [ + 0.0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "ace34590f116bea6a9b3", + "type": "text", + "x": 489.929203539823, + "y": 189.0, + "width": 297.59999999999997, + "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": "35.7 ms to start and do nothing", + "originalText": "35.7 ms to start and do nothing", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "23746c155c94284bc39f", + "type": "text", + "x": 0.0, + "y": 236.0, + "width": 748.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": "A debug build turns frozen modules off, so it reads from disk what release does not.", + "originalText": "A debug build turns frozen modules off, so it reads from disk what release does not.", + "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/r01-before-your-first-line/diagrams/what-startup-costs.svg b/lessons/r01-before-your-first-line/diagrams/what-startup-costs.svg new file mode 100644 index 0000000..183e902 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/what-startup-costs.svg @@ -0,0 +1 @@ +Starting an interpreter that runs no code of yoursrelease, normal26.5 ms to start and do nothingrelease, -S20.1 ms to start and do nothingdebug, normal56.5 ms to start and do nothingdebug, -S35.7 ms to start and do nothingA debug build turns frozen modules off, so it reads from disk what release does not. diff --git a/lessons/r01-before-your-first-line/diagrams/who-works-out-sys-path.excalidraw b/lessons/r01-before-your-first-line/diagrams/who-works-out-sys-path.excalidraw new file mode 100644 index 0000000..33279ae --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/who-works-out-sys-path.excalidraw @@ -0,0 +1,746 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "bea4f26195cff2cd7eec", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 860.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": "sys.path is produced by a Python program that is not on sys.path", + "originalText": "sys.path is produced by a Python program that is not on sys.path", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "c33b5a95a3dc7bdd65a0", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 305.0, + "height": 70.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "8445db7cff52b4fdfbf4", + "type": "text" + }, + { + "id": "713d9f49a59b4f899dbd", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "8445db7cff52b4fdfbf4", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 273.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": "Modules/getpath.py", + "originalText": "Modules/getpath.py", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c33b5a95a3dc7bdd65a0", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "e7e0e98662b0d8164d94", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 305.0, + "height": 70.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "3a557ec987c88f4bc05f", + "type": "text" + }, + { + "id": "713d9f49a59b4f899dbd", + "type": "arrow" + }, + { + "id": "76648cf0bf2739ada084", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "3a557ec987c88f4bc05f", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 273.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": "compiled at build time", + "originalText": "compiled at build time", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "e7e0e98662b0d8164d94", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "b09ad0eeea5552021a6e", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 305.0, + "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": "1f324340fba65acb41dc", + "type": "text" + }, + { + "id": "76648cf0bf2739ada084", + "type": "arrow" + }, + { + "id": "64630594a8b8da316e74", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "1f324340fba65acb41dc", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 273.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "a byte blob in the binary", + "originalText": "a byte blob in the binary", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "b09ad0eeea5552021a6e", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "dbe8e9074e95dbef2796", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 305.0, + "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": "9bbeb7907315767f2465", + "type": "text" + }, + { + "id": "64630594a8b8da316e74", + "type": "arrow" + }, + { + "id": "f98db4f224cdbc7db420", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "9bbeb7907315767f2465", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 273.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": "run during startup", + "originalText": "run during startup", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "dbe8e9074e95dbef2796", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "d392fd23bd4fd3335e3f", + "type": "rectangle", + "x": 0.0, + "y": 570.0, + "width": 305.0, + "height": 70.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "e48a185da7464024e5e5", + "type": "text" + }, + { + "id": "f98db4f224cdbc7db420", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "e48a185da7464024e5e5", + "type": "text", + "x": 16.0, + "y": 592.5, + "width": 273.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": "sys.path and friends", + "originalText": "sys.path and friends", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "d392fd23bd4fd3335e3f", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "713d9f49a59b4f899dbd", + "type": "arrow", + "x": 152.5, + "y": 120.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "c33b5a95a3dc7bdd65a0", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "e7e0e98662b0d8164d94", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "98aedbdf067decf14f4f", + "type": "text", + "x": 164.5, + "y": 140.0, + "width": 98.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": "to bytecode", + "originalText": "to bytecode", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "76648cf0bf2739ada084", + "type": "arrow", + "x": 152.5, + "y": 250.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "e7e0e98662b0d8164d94", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "b09ad0eeea5552021a6e", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "de69fa5a5705657720c3", + "type": "text", + "x": 164.5, + "y": 270.0, + "width": 116.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": "marshalled in", + "originalText": "marshalled in", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "64630594a8b8da316e74", + "type": "arrow", + "x": 152.5, + "y": 380.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "b09ad0eeea5552021a6e", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "dbe8e9074e95dbef2796", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "af67cf701e7984ab6919", + "type": "text", + "x": 164.5, + "y": 400.0, + "width": 322.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": "eleven C helpers stand in for os.path", + "originalText": "eleven C helpers stand in for os.path", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f98db4f224cdbc7db420", + "type": "arrow", + "x": 152.5, + "y": 510.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "dbe8e9074e95dbef2796", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "d392fd23bd4fd3335e3f", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/r01-before-your-first-line/diagrams/who-works-out-sys-path.svg b/lessons/r01-before-your-first-line/diagrams/who-works-out-sys-path.svg new file mode 100644 index 0000000..1983967 --- /dev/null +++ b/lessons/r01-before-your-first-line/diagrams/who-works-out-sys-path.svg @@ -0,0 +1 @@ +sys.path is produced by a Python program that is not on sys.pathModules/getpath.pycompiled at build timea byte blob in the binaryrun during startupsys.path and friendsto bytecodemarshalled ineleven C helpers stand in for os.path diff --git a/lessons/r01-before-your-first-line/r01.ipynb b/lessons/r01-before-your-first-line/r01.ipynb new file mode 100644 index 0000000..8ef78b9 --- /dev/null +++ b/lessons/r01-before-your-first-line/r01.ipynb @@ -0,0 +1,761 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "r01-01", + "metadata": {}, + "source": [ + "# R01. Before your first line\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/r01-before-your-first-line/r01.ipynb)\n", + "\n", + "By the time your first statement runs, a lot has already happened. The interpreter has read a configuration out of three different places, built a runtime, made an interpreter, imported a few dozen modules without touching the disk once, worked out where the standard library lives by running a Python program that is not on `sys.path`, and then decided what to put at the front of `sys.path` afterwards.\n", + "\n", + "None of that is your code, and all of it is on your clock. This lesson goes through it in the order it happens.\n", + "\n", + "![a flow from the shell running python through configuration, runtime, core startup and main startup to your first line](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/r01-before-your-first-line/diagrams/before-your-first-line.svg)" + ] + }, + { + "cell_type": "markdown", + "id": "r01-02", + "metadata": {}, + "source": [ + "## About the source references\n", + "\n", + "Now and then this lesson points at CPython's own source, like this: `Modules/getpath.c:855-894@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": "r01-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": "r01-04", + "metadata": {}, + "source": [ + "## Which Python is this\n", + "\n", + "Almost every cell here starts a fresh child interpreter and asks it a question, because the only honest way to look at startup is to watch somebody else do it. Your notebook finished starting up long ago, and it has since imported hundreds of modules that have nothing to do with the subject.\n", + "\n", + "Some runtimes cannot start a process at all. A browser tab is the usual example. Each cell checks first and says so rather than failing, so the notebook still reads through on a runtime that cannot run it.\n", + "\n", + "## Which interpreter is this" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "r01-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": "r01-06", + "metadata": {}, + "source": [ + "## What is already there\n", + "\n", + "Start with the inventory. A brand new interpreter that has been asked to run nothing at all still has a populated `sys.modules`, and the interesting question is not how many but where they came from.\n", + "\n", + "Every module carries a `__spec__` with an `origin` on it. Three answers matter. `built-in` means the module is C code compiled straight into the `python` binary. `frozen` means it is Python source that was compiled to bytecode when CPython was built, and that bytecode is also inside the binary. Anything else is a path, which means somebody opened a file.\n", + "\n", + "A fresh interpreter has a few dozen modules in sys.modules before it runs any of your code, and with site turned off not one of them was read from a file on disk" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "r01-07", + "metadata": { + "cpython_internals": { + "varies": "the counts move a little between releases and between builds, and a debug build reports far fewer frozen modules for a reason the last section of this lesson gets to" + } + }, + "outputs": [], + "source": [ + "import subprocess\n", + "\n", + "COUNT = \"\"\"\n", + "import sys\n", + "kinds = {\"built-in\": 0, \"frozen\": 0, \"from a file\": 0}\n", + "named = []\n", + "for name, module in sys.modules.items():\n", + " origin = getattr(getattr(module, \"__spec__\", None), \"origin\", None)\n", + " if origin in (\"built-in\", \"frozen\"):\n", + " kinds[origin] += 1\n", + " elif origin is not None:\n", + " kinds[\"from a file\"] += 1\n", + " named.append(name)\n", + "print(len(sys.modules), kinds[\"built-in\"], kinds[\"frozen\"], kinds[\"from a file\"])\n", + "print(\" \".join(named) or \"none at all\")\n", + "\"\"\"\n", + "\n", + "\n", + "def child(flags, code):\n", + " \"\"\"Start a brand new interpreter with those flags and hand back what it printed.\"\"\"\n", + " done = subprocess.run(\n", + " [sys.executable, *flags, \"-c\", code], capture_output=True, text=True, check=True\n", + " )\n", + " return done.stdout.rstrip()\n", + "\n", + "\n", + "def children_work():\n", + " \"\"\"Some runtimes cannot start a process at all. A browser tab is one of them.\"\"\"\n", + " try:\n", + " child([], \"pass\")\n", + " except Exception:\n", + " return False\n", + " return True\n", + "\n", + "\n", + "CHILDREN = children_work()\n", + "NO_CHILDREN = \" this runtime cannot start another interpreter, so there is nothing to look at\"\n", + "\n", + "if not CHILDREN:\n", + " print(NO_CHILDREN)\n", + "else:\n", + " for what, flags in [(\"a normal start\", []), (\"started with -S\", [\"-S\"])]:\n", + " counts, names = child(flags, COUNT).splitlines()\n", + " total, builtin, frozen, files = counts.split()\n", + " print(f\" {what:18} {total:>3} modules in sys.modules\", end=\"\")\n", + " print(f\": {builtin} built in, {frozen} frozen, {files} from a file\")\n", + " print(f\" {'':18} the ones from a file: {names}\")" + ] + }, + { + "cell_type": "markdown", + "id": "r01-08", + "metadata": {}, + "source": [ + "> **Version note.** the counts move a little between releases and between builds, and a debug build reports far fewer frozen modules for a reason the last section of this lesson gets to" + ] + }, + { + "cell_type": "markdown", + "id": "r01-09", + "metadata": {}, + "source": [ + "![a table of how each startup module got there, counted for a normal start and for a start with -S](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/r01-before-your-first-line/diagrams/what-is-already-imported.svg)\n", + "\n", + "Look at the `-S` row first, because that is the interpreter on its own. Nothing came off the disk. That is the whole point of freezing: the interpreter cannot import `os` from a file until it knows where files are, and it cannot know where files are until it has imported `os`. Putting the bytecode in the binary breaks the circle.\n", + "\n", + "The difference between the two rows is `site`, the module that adds `site-packages` to your path and runs any `.pth` files it finds. If your first row listed some modules as coming from a file, those are the ones `site` brought in for you: a virtual environment hook, a `sitecustomize`, whatever your installation puts there. They are yours, not the interpreter's. `site` is not free either, and it is the first thing to turn off if you are starting a lot of short lived interpreters.\n", + "\n", + "## Two halves, and why there are two\n", + "\n", + "The `python` binary lands in `pymain_main`, which does two things and no more: get ready, then run [Modules/main.c:855-868@v3.15.0rc1#pymain_main](https://github.com/python/cpython/blob/v3.15.0rc1/Modules/main.c#L855-L868). Getting ready means turning your command line and your environment into a `PyConfig` and handing it over [Modules/main.c:37-76@v3.15.0rc1#pymain_init](https://github.com/python/cpython/blob/v3.15.0rc1/Modules/main.c#L37-L76). Running means `Py_RunMain`, which works out whether you gave it a script, a module, a string or nothing at all, does that, and then shuts the interpreter down again [Modules/main.c:831-852@v3.15.0rc1#Py_RunMain](https://github.com/python/cpython/blob/v3.15.0rc1/Modules/main.c#L831-L852).\n", + "\n", + "The getting ready part is where the work is, and it comes in two halves [Python/pylifecycle.c:1609-1639@v3.15.0rc1#Py_InitializeFromConfig](https://github.com/python/cpython/blob/v3.15.0rc1/Python/pylifecycle.c#L1609-L1639). This is the [two phase initialisation](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#two-phase-initialisation) that the C API documentation keeps mentioning. Core startup builds the runtime, the main interpreter and the built in types, and it has to manage all of that with no import system, no `sys.path` and no codecs [Python/pylifecycle.c:1150-1172@v3.15.0rc1#pyinit_core](https://github.com/python/cpython/blob/v3.15.0rc1/Python/pylifecycle.c#L1150-L1172). Main startup is everything that needs those, which includes working out `sys.path` in the first place [Python/pylifecycle.c:1589-1606@v3.15.0rc1#pyinit_main](https://github.com/python/cpython/blob/v3.15.0rc1/Python/pylifecycle.c#L1589-L1606).\n", + "\n", + "That split explains something you have probably seen. A bad configuration comes out as a fatal error with a plain C string rather than as a Python traceback, because at the moment the problem is noticed there is nothing to raise and nothing to print it with.\n", + "\n", + "You can watch the second half happen. `-X importtime` prints one line per import with the time it took, and for an interpreter running no code at all, every line it prints is startup.\n", + "\n", + "The imports that happen before your first line add up to a few milliseconds, and the ones that show up include the import machinery, the codecs, os and site" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "r01-10", + "metadata": { + "cpython_internals": { + "varies": "which imports clear the threshold depends on your disk and your machine, and an install with a sitecustomize module of its own will show that too" + } + }, + "outputs": [], + "source": [ + "if not CHILDREN:\n", + " print(NO_CHILDREN)\n", + "else:\n", + " done = subprocess.run(\n", + " [sys.executable, \"-X\", \"importtime\", \"-c\", \"pass\"], capture_output=True, text=True\n", + " )\n", + " total = 0\n", + " for line in done.stderr.splitlines():\n", + " parts = line.split(\"|\")\n", + " if len(parts) != 3:\n", + " continue\n", + " head = parts[0].removeprefix(\"import time:\").strip()\n", + " if not head.isdigit():\n", + " continue\n", + " total += int(head)\n", + " if int(head) > 300:\n", + " print(f\" {parts[2].strip():30} {int(head) / 1000:5.1f} ms on its own\")\n", + " print(f\" {'all of them, added up':30} {total / 1000:5.1f} ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "r01-11", + "metadata": {}, + "source": [ + "> **Version note.** which imports clear the threshold depends on your disk and your machine, and an install with a sitecustomize module of its own will show that too" + ] + }, + { + "cell_type": "markdown", + "id": "r01-12", + "metadata": {}, + "source": [ + "The third column is the module, and the number is its own time rather than its children's. `site` looks cheap on that line and is not cheap overall, because most of what it costs is the imports it triggers underneath.\n", + "\n", + "## The configuration comes from three places\n", + "\n", + "A setting can be asked for on the command line, in the environment, or by whoever embedded the interpreter and filled in the `PyConfig` struct directly. When two of them disagree, the natural guess is that the nearest one wins. The guess is wrong.\n", + "\n", + "The helper that reads a flag out of the environment is a few lines long, and the line that matters is `if (*flag < value) *flag = value;` [Python/preconfig.c:560-578@v3.15.0rc1#_Py_get_env_flag](https://github.com/python/cpython/blob/v3.15.0rc1/Python/preconfig.c#L560-L578). It raises a floor. It never lowers anything. `PYTHONOPTIMIZE` goes through the same shape of code [Python/initconfig.c:1995-2020@v3.15.0rc1#PYTHONOPTIMIZE](https://github.com/python/cpython/blob/v3.15.0rc1/Python/initconfig.c#L1995-L2020), and on the command line `-O` increments the level rather than assigning it, which is what makes `-OO` mean two [Python/initconfig.c:3210-3222@v3.15.0rc1#optimization_level](https://github.com/python/cpython/blob/v3.15.0rc1/Python/initconfig.c#L3210-L3222).\n", + "\n", + "Put those together and the rule is simple to say and easy to forget. The highest number anybody asked for is the one you get. There is one way out, which is `-E`: it throws the environment away before any of this happens, so it does not beat the environment, it deletes it.\n", + "\n", + "When the command line and the environment disagree about the optimization level, neither wins by being nearer, the higher of the two is what you get, and -E removes the environment from the argument entirely" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "r01-13", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "ASK = \"import sys; print(sys.flags.optimize)\"\n", + "BASE = {k: v for k, v in os.environ.items() if k != \"PYTHONOPTIMIZE\"}\n", + "\n", + "if not CHILDREN:\n", + " print(NO_CHILDREN)\n", + "else:\n", + " for what, flags, extra in [\n", + " (\"nothing at all\", [], {}),\n", + " (\"-O\", [\"-O\"], {}),\n", + " (\"-OO\", [\"-OO\"], {}),\n", + " (\"PYTHONOPTIMIZE=2\", [], {\"PYTHONOPTIMIZE\": \"2\"}),\n", + " (\"PYTHONOPTIMIZE=2 and -O\", [\"-O\"], {\"PYTHONOPTIMIZE\": \"2\"}),\n", + " (\"PYTHONOPTIMIZE=1 and -OO\", [\"-OO\"], {\"PYTHONOPTIMIZE\": \"1\"}),\n", + " (\"PYTHONOPTIMIZE=2 and -E\", [\"-E\"], {\"PYTHONOPTIMIZE\": \"2\"}),\n", + " ]:\n", + " done = subprocess.run(\n", + " [sys.executable, *flags, \"-c\", ASK],\n", + " capture_output=True,\n", + " text=True,\n", + " env={**BASE, **extra},\n", + " )\n", + " print(f\" {what:26} sys.flags.optimize is {done.stdout.strip()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "r01-14", + "metadata": {}, + "source": [ + "![a table of what you asked for, what you might expect sys.flags.optimize to be, and what it actually is](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/r01-before-your-first-line/diagrams/the-highest-setting-wins.svg)\n", + "\n", + "The two middle rows are the ones worth remembering. A `PYTHONOPTIMIZE=2` left in a shell profile years ago is not overridden by putting `-O` on the command line today, and nothing tells you.\n", + "\n", + "## sys.path is worked out by a Python program that is not on sys.path\n", + "\n", + "Here is the part that surprises people. `sys.path` is not computed in C. It is computed by a Python program, `Modules/getpath.py`, which is compiled to bytecode when CPython itself is built and stored in the binary as a [marshal](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#marshal)led blob [Modules/getpath.c:830-840@v3.15.0rc1#_Py_Get_Getpath_CodeObject](https://github.com/python/cpython/blob/v3.15.0rc1/Modules/getpath.c#L830-L840). During main startup that blob is turned back into a code object and evaluated [Modules/getpath.c:855-894@v3.15.0rc1#_PyConfig_InitPathConfig](https://github.com/python/cpython/blob/v3.15.0rc1/Modules/getpath.c#L855-L894), and what comes out is the whole [path configuration](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#path-configuration): `sys.prefix`, `sys.exec_prefix`, `sys.executable`, `sys._base_executable` and `sys.path`.\n", + "\n", + "It cannot import anything, for the obvious reason that `sys.path` is what it is there to produce. So it is handed eleven C functions to stand in for the `os.path` it cannot have: `abspath`, `basename`, `dirname`, `hassuffix`, `isabs`, `isdir`, `isfile`, `isxfile`, `joinpath`, `readlines` and `realpath` [Modules/getpath.c:560-582@v3.15.0rc1#getpath_methods](https://github.com/python/cpython/blob/v3.15.0rc1/Modules/getpath.c#L560-L582). That list is the entire vocabulary the path search has to work with.\n", + "\n", + "![a flow from Modules getpath dot py through compilation and marshalling to the values it produces at startup](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/r01-before-your-first-line/diagrams/who-works-out-sys-path.svg)\n", + "\n", + "The output has one entry in it that people misread constantly. There is always a `pythonXY.zip` next to the standard library, and on a normal install that file does not exist. It is not a bug and it is not left over from anything. Putting a zip file there is a supported way to ship a whole standard library in one file, so the entry is added unconditionally and the import system simply finds nothing when it looks.\n", + "\n", + "sys.path contains an entry for a zip file next to the standard library, and on an ordinary install that file is not there" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "r01-15", + "metadata": { + "cpython_internals": { + "varies": "every path here is where your Python happens to be installed, and inside a virtual environment sys.prefix and sys.base_prefix point at different places" + } + }, + "outputs": [], + "source": [ + "WHERE = \"\"\"\n", + "import os\n", + "import sys\n", + "print(\" sys.executable \", sys.executable)\n", + "print(\" sys._base_executable \", sys._base_executable)\n", + "print(\" sys.prefix \", sys.prefix)\n", + "print(\" sys.base_prefix \", sys.base_prefix)\n", + "for entry in sys.path:\n", + " shown = entry or \"(the empty string)\"\n", + " print(\" sys.path \", shown, \"exists:\", os.path.exists(entry))\n", + "\"\"\"\n", + "\n", + "if not CHILDREN:\n", + " print(NO_CHILDREN)\n", + "else:\n", + " print(child([], WHERE))" + ] + }, + { + "cell_type": "markdown", + "id": "r01-16", + "metadata": {}, + "source": [ + "> **Version note.** every path here is where your Python happens to be installed, and inside a virtual environment sys.prefix and sys.base_prefix point at different places" + ] + }, + { + "cell_type": "markdown", + "id": "r01-17", + "metadata": {}, + "source": [ + "If you ran that inside a virtual environment, `sys.prefix` and `sys.base_prefix` came out different. That difference is the whole mechanism: a virtual environment is a `pyvenv.cfg` file that tells `getpath.py` where the real installation is, and everything else follows from the two prefixes.\n", + "\n", + "## The front of sys.path is decided last\n", + "\n", + "Everything above happens while the interpreter is starting. The front of `sys.path` does not. It is pushed on afterwards, once the interpreter is already running, by the code that is about to hand control to your program [Modules/main.c:657-696@v3.15.0rc1#pymain_run_python](https://github.com/python/cpython/blob/v3.15.0rc1/Modules/main.c#L657-L696).\n", + "\n", + "What goes in depends on how you started. The directory the script is in, for a script. The current directory, for `-m`. The empty string, for `-c` and for the interactive prompt, which the import system reads as the current directory at the time of each import.\n", + "\n", + "This is the cause of the most common confusion in Python. A file called `random.py` sitting next to your script shadows the standard library module of the same name, and it does so because its directory was put in front of the standard library after the standard library had already been located.\n", + "\n", + "There is a switch for it. `-P` on the command line, or `PYTHONSAFEPATH` in the environment, is the [safe path](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#safe-path) option [Modules/main.c:696-728@v3.15.0rc1#safe_path](https://github.com/python/cpython/blob/v3.15.0rc1/Modules/main.c#L696-L728). Note what it does: the entry is never added, rather than added and then taken away. So `sys.path[0]` becomes whatever was going to be first anyway, which is that zip file from the previous section.\n", + "\n", + "With -P the entry that would have gone on the front of sys.path is never added at all, so sys.path[0] becomes the first of the standard library entries instead" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "r01-18", + "metadata": { + "cpython_internals": { + "varies": "the temporary directory is different every run, and on macOS the child reports it with the symbolic links resolved so it looks slightly different from the last line" + } + }, + "outputs": [], + "source": [ + "import pathlib\n", + "import tempfile\n", + "\n", + "SHOW = \"import sys; print(repr(sys.path[0]))\"\n", + "\n", + "if not CHILDREN:\n", + " print(NO_CHILDREN)\n", + "else:\n", + " with tempfile.TemporaryDirectory() as folder:\n", + " (pathlib.Path(folder) / \"show.py\").write_text(SHOW)\n", + " script = str(pathlib.Path(folder) / \"show.py\")\n", + " for what, args, where in [\n", + " (\"python -c code\", [\"-c\", SHOW], None),\n", + " (\"python show.py\", [script], None),\n", + " (\"python -m show\", [\"-m\", \"show\"], folder),\n", + " (\"python -P show.py\", [\"-P\", script], None),\n", + " ]:\n", + " done = subprocess.run(\n", + " [sys.executable, *args], capture_output=True, text=True, cwd=where\n", + " )\n", + " print(f\" {what:22} {done.stdout.strip()}\")\n", + " print(f\" the script is in {folder!r}\")" + ] + }, + { + "cell_type": "markdown", + "id": "r01-19", + "metadata": {}, + "source": [ + "> **Version note.** the temporary directory is different every run, and on macOS the child reports it with the symbolic links resolved so it looks slightly different from the last line" + ] + }, + { + "cell_type": "markdown", + "id": "r01-20", + "metadata": {}, + "source": [ + "![a table of how you started python, what lands in sys path index zero, and when it was added](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/r01-before-your-first-line/diagrams/what-goes-in-front.svg)\n", + "\n", + "The last row is the one to reach for when something is being shadowed and you cannot work out what. If the problem goes away under `-P`, a file in your own directory was the cause.\n", + "\n", + "## What all of it costs\n", + "\n", + "Now the clock. The cell below starts an interpreter that runs nothing, ten times, and keeps the fastest, because the fastest run is the one where the machine was not busy doing something else.\n", + "\n", + "Starting an interpreter that runs none of your code takes tens of milliseconds, and -S takes a noticeable slice off that because site is a real import" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "r01-21", + "metadata": { + "cpython_internals": { + "varies": "startup time depends on your disk, your machine and whether the files are still in the operating system cache, so only the gaps between the three rows mean anything" + } + }, + "outputs": [], + "source": [ + "import time\n", + "\n", + "\n", + "def best(flags, rounds=10):\n", + " \"\"\"The fastest of a few runs, which is the honest number on a machine you share.\"\"\"\n", + " fastest = None\n", + " for _ in range(rounds):\n", + " started = time.perf_counter()\n", + " subprocess.run([sys.executable, *flags, \"-c\", \"pass\"], capture_output=True, check=True)\n", + " taken = time.perf_counter() - started\n", + " fastest = taken if fastest is None else min(fastest, taken)\n", + " return fastest\n", + "\n", + "\n", + "if not CHILDREN:\n", + " print(NO_CHILDREN)\n", + "else:\n", + " for what, flags in [\n", + " (\"everything\", []),\n", + " (\"no site, with -S\", [\"-S\"]),\n", + " (\"no site and no environment\", [\"-I\", \"-S\"]),\n", + " ]:\n", + " print(f\" {what:28} {best(flags) * 1000:5.1f} ms\")" + ] + }, + { + "cell_type": "markdown", + "id": "r01-22", + "metadata": {}, + "source": [ + "> **Version note.** startup time depends on your disk, your machine and whether the files are still in the operating system cache, so only the gaps between the three rows mean anything" + ] + }, + { + "cell_type": "markdown", + "id": "r01-23", + "metadata": {}, + "source": [ + "Those numbers include the cost of your machine starting a process at all, which is not the interpreter's fault, so treat the differences between the rows as the real content.\n", + "\n", + "The two recordings below run the same program in two containers built from the same source. One is an ordinary release build. The other was configured with `--with-pydebug`, which is the build you would use if you were working on CPython itself.\n", + "\n", + "How much is already loaded before your program starts, and how long did that take?\n", + "\n", + "```python\n", + "\"\"\"What has already happened before your first line runs, and what it cost.\n", + "\n", + "Nothing here is about your code. Every number is the interpreter getting itself ready: reading a\n", + "configuration, building a runtime, creating the main interpreter, importing the modules it cannot\n", + "run without, working out where the standard library is, and importing site.\n", + "\n", + "The module counts come from a child process asking itself what is in sys.modules before it does\n", + "anything. The timings are the fastest of twenty runs of a child that does nothing at all, so what\n", + "is being measured is startup and only startup.\n", + "\n", + "Running this on a debug build as well as a release one is the point. The work is identical, the\n", + "counts come out the same, and the clock does not, which puts a number on what the assertions and\n", + "the reference count bookkeeping cost before your program even begins.\n", + "\"\"\"\n", + "\n", + "import subprocess\n", + "import sys\n", + "import time\n", + "\n", + "COUNT = \"\"\"\n", + "import sys\n", + "kinds = {\"built-in\": 0, \"frozen\": 0, \"from a file\": 0}\n", + "for module in sys.modules.values():\n", + " origin = getattr(getattr(module, \"__spec__\", None), \"origin\", None)\n", + " if origin in (\"built-in\", \"frozen\"):\n", + " kinds[origin] += 1\n", + " elif origin is not None:\n", + " kinds[\"from a file\"] += 1\n", + "print(len(sys.modules), kinds[\"built-in\"], kinds[\"frozen\"], kinds[\"from a file\"])\n", + "\"\"\"\n", + "\n", + "WAYS = [\n", + " (\"everything\", []),\n", + " (\"no site\", [\"-S\"]),\n", + " (\"isolated and no site\", [\"-I\", \"-S\"]),\n", + "]\n", + "\n", + "\n", + "def child(flags, code):\n", + " \"\"\"Run a fresh interpreter with those flags and hand back what it printed.\"\"\"\n", + " done = subprocess.run(\n", + " [sys.executable, *flags, \"-c\", code], capture_output=True, text=True, check=True\n", + " )\n", + " return done.stdout.strip()\n", + "\n", + "\n", + "def best(flags, rounds=20):\n", + " \"\"\"The fastest of a few runs, which is the honest number on a shared machine.\"\"\"\n", + " fastest = None\n", + " for _ in range(rounds):\n", + " started = time.perf_counter()\n", + " subprocess.run([sys.executable, *flags, \"-c\", \"pass\"], capture_output=True, check=True)\n", + " taken = time.perf_counter() - started\n", + " fastest = taken if fastest is None else min(fastest, taken)\n", + " return fastest\n", + "\n", + "\n", + "def import_cost():\n", + " \"\"\"Add up the self times that -X importtime prints, in milliseconds.\"\"\"\n", + " done = subprocess.run(\n", + " [sys.executable, \"-X\", \"importtime\", \"-c\", \"pass\"], capture_output=True, text=True\n", + " )\n", + " total = 0\n", + " for line in done.stderr.splitlines():\n", + " parts = line.split(\"|\")\n", + " if len(parts) == 3 and parts[0].startswith(\"import time:\"):\n", + " head = parts[0].removeprefix(\"import time:\").strip()\n", + " if head.isdigit():\n", + " total += int(head)\n", + " return total / 1000\n", + "\n", + "\n", + "print(f\"~ this is a debug build: {hasattr(sys, 'gettotalrefcount')}\")\n", + "\n", + "for label, flags in WAYS:\n", + " total, builtin, frozen, files = child(flags, COUNT).split()\n", + " print(f\"modules at the first line, {label}: {total}\")\n", + " print(f\" of those, built into the binary: {builtin}\")\n", + " print(f\" of those, frozen bytecode: {frozen}\")\n", + " print(f\" of those, read from a file on disk: {files}\")\n", + "\n", + "for label, flags in WAYS:\n", + " print(f\"~ starting up, {label}: {best(flags) * 1000:.1f} ms\")\n", + "\n", + "print(f\"~ of that, spent importing: {import_cost():.1f} ms\")\n", + "```\n", + "\n", + "```text\n", + "~ this is a debug build: False\n", + "modules at the first line, everything: 33\n", + " of those, built into the binary: 15\n", + " of those, frozen bytecode: 17\n", + " of those, read from a file on disk: 0\n", + "modules at the first line, no site: 21\n", + " of those, built into the binary: 12\n", + " of those, frozen bytecode: 8\n", + " of those, read from a file on disk: 0\n", + "modules at the first line, isolated and no site: 21\n", + " of those, built into the binary: 12\n", + " of those, frozen bytecode: 8\n", + " of those, read from a file on disk: 0\n", + "~ starting up, everything: 26.5 ms\n", + "~ starting up, no site: 20.1 ms\n", + "~ starting up, isolated and no site: 21.0 ms\n", + "~ of that, spent importing: 11.6 ms\n", + "```\n", + "\n", + "That ran on Python 3.15.0rc1 in the release build this project publishes, which is `ghcr.io/tamnd/cpython-internals/cpython:release@sha256:fb55d6afcf053c974de6447fafbd2be6af20cdb9f596e25a0445607b8af981e3`. You do not need that build to read the numbers, and you do need it to produce them, which is why this is recorded rather than left as a cell you run. If you want to watch it happen yourself, `docker run --rm -i ghcr.io/tamnd/cpython-internals/cpython:release@sha256:fb55d6afcf053c974de6447fafbd2be6af20cdb9f596e25a0445607b8af981e3 python3 -` takes the program on standard input.\n", + "\n", + "Does a debug build do more work at startup, or the same work more slowly?\n", + "\n", + "```python\n", + "\"\"\"What has already happened before your first line runs, and what it cost.\n", + "\n", + "Nothing here is about your code. Every number is the interpreter getting itself ready: reading a\n", + "configuration, building a runtime, creating the main interpreter, importing the modules it cannot\n", + "run without, working out where the standard library is, and importing site.\n", + "\n", + "The module counts come from a child process asking itself what is in sys.modules before it does\n", + "anything. The timings are the fastest of twenty runs of a child that does nothing at all, so what\n", + "is being measured is startup and only startup.\n", + "\n", + "Running this on a debug build as well as a release one is the point. The work is identical, the\n", + "counts come out the same, and the clock does not, which puts a number on what the assertions and\n", + "the reference count bookkeeping cost before your program even begins.\n", + "\"\"\"\n", + "\n", + "import subprocess\n", + "import sys\n", + "import time\n", + "\n", + "COUNT = \"\"\"\n", + "import sys\n", + "kinds = {\"built-in\": 0, \"frozen\": 0, \"from a file\": 0}\n", + "for module in sys.modules.values():\n", + " origin = getattr(getattr(module, \"__spec__\", None), \"origin\", None)\n", + " if origin in (\"built-in\", \"frozen\"):\n", + " kinds[origin] += 1\n", + " elif origin is not None:\n", + " kinds[\"from a file\"] += 1\n", + "print(len(sys.modules), kinds[\"built-in\"], kinds[\"frozen\"], kinds[\"from a file\"])\n", + "\"\"\"\n", + "\n", + "WAYS = [\n", + " (\"everything\", []),\n", + " (\"no site\", [\"-S\"]),\n", + " (\"isolated and no site\", [\"-I\", \"-S\"]),\n", + "]\n", + "\n", + "\n", + "def child(flags, code):\n", + " \"\"\"Run a fresh interpreter with those flags and hand back what it printed.\"\"\"\n", + " done = subprocess.run(\n", + " [sys.executable, *flags, \"-c\", code], capture_output=True, text=True, check=True\n", + " )\n", + " return done.stdout.strip()\n", + "\n", + "\n", + "def best(flags, rounds=20):\n", + " \"\"\"The fastest of a few runs, which is the honest number on a shared machine.\"\"\"\n", + " fastest = None\n", + " for _ in range(rounds):\n", + " started = time.perf_counter()\n", + " subprocess.run([sys.executable, *flags, \"-c\", \"pass\"], capture_output=True, check=True)\n", + " taken = time.perf_counter() - started\n", + " fastest = taken if fastest is None else min(fastest, taken)\n", + " return fastest\n", + "\n", + "\n", + "def import_cost():\n", + " \"\"\"Add up the self times that -X importtime prints, in milliseconds.\"\"\"\n", + " done = subprocess.run(\n", + " [sys.executable, \"-X\", \"importtime\", \"-c\", \"pass\"], capture_output=True, text=True\n", + " )\n", + " total = 0\n", + " for line in done.stderr.splitlines():\n", + " parts = line.split(\"|\")\n", + " if len(parts) == 3 and parts[0].startswith(\"import time:\"):\n", + " head = parts[0].removeprefix(\"import time:\").strip()\n", + " if head.isdigit():\n", + " total += int(head)\n", + " return total / 1000\n", + "\n", + "\n", + "print(f\"~ this is a debug build: {hasattr(sys, 'gettotalrefcount')}\")\n", + "\n", + "for label, flags in WAYS:\n", + " total, builtin, frozen, files = child(flags, COUNT).split()\n", + " print(f\"modules at the first line, {label}: {total}\")\n", + " print(f\" of those, built into the binary: {builtin}\")\n", + " print(f\" of those, frozen bytecode: {frozen}\")\n", + " print(f\" of those, read from a file on disk: {files}\")\n", + "\n", + "for label, flags in WAYS:\n", + " print(f\"~ starting up, {label}: {best(flags) * 1000:.1f} ms\")\n", + "\n", + "print(f\"~ of that, spent importing: {import_cost():.1f} ms\")\n", + "```\n", + "\n", + "```text\n", + "~ this is a debug build: True\n", + "modules at the first line, everything: 33\n", + " of those, built into the binary: 15\n", + " of those, frozen bytecode: 3\n", + " of those, read from a file on disk: 14\n", + "modules at the first line, no site: 21\n", + " of those, built into the binary: 12\n", + " of those, frozen bytecode: 3\n", + " of those, read from a file on disk: 5\n", + "modules at the first line, isolated and no site: 21\n", + " of those, built into the binary: 12\n", + " of those, frozen bytecode: 3\n", + " of those, read from a file on disk: 5\n", + "~ starting up, everything: 56.5 ms\n", + "~ starting up, no site: 35.7 ms\n", + "~ starting up, isolated and no site: 33.9 ms\n", + "~ of that, spent importing: 30.8 ms\n", + "```\n", + "\n", + "That ran on Python 3.15.0rc1 in the debug build this project publishes, which is `ghcr.io/tamnd/cpython-internals/cpython:debug@sha256:7baea8f3dd4de2e4c3b020543729b147e636494ae9758dabffb4675793e37170`. You do not need that build to read the numbers, and you do need it to produce them, which is why this is recorded rather than left as a cell you run. If you want to watch it happen yourself, `docker run --rm -i ghcr.io/tamnd/cpython-internals/cpython:debug@sha256:7baea8f3dd4de2e4c3b020543729b147e636494ae9758dabffb4675793e37170 python3 -` takes the program on standard input.\n", + "\n", + "![bars of milliseconds to start and do nothing, for a release build and a debug build, each with and without site](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/r01-before-your-first-line/diagrams/what-startup-costs.svg)\n", + "\n", + "The debug build takes 56.5 ms against 26.5 ms. The obvious explanation is the assertions and the reference count bookkeeping, and the obvious explanation is mostly wrong. Look at the module counts instead. The release build reports 17 frozen and 0 from a file. The debug build reports 3 frozen and 14 from a file. That is not the same work done more slowly, it is different work.\n", + "\n", + "A debug build turns frozen modules off, and it is one `#ifdef` in the defaults [Python/initconfig.c:1193-1201@v3.15.0rc1#use_frozen_modules](https://github.com/python/cpython/blob/v3.15.0rc1/Python/initconfig.c#L1193-L1201). The reasoning is good: if you are debugging CPython you want to step through the real `Lib/os.py` on disk, not through bytecode that was baked into the binary at build time. The price is that `os`, `site`, `codecs` and everything else frozen now comes off the disk, and the import bill goes from 11.6 ms to 30.8 ms. You can put it back with `-X frozen_modules=on`, or with `PYTHON_FROZEN_MODULES=on` in the environment [Python/initconfig.c:2849-2866@v3.15.0rc1#frozen_modules](https://github.com/python/cpython/blob/v3.15.0rc1/Python/initconfig.c#L2849-L2866).\n", + "\n", + "A debug build starts in about twice the time of a release build, and most of the extra is not the assertions, it is the fourteen modules it reads off the disk because a debug build turns frozen modules off" + ] + }, + { + "cell_type": "markdown", + "id": "r01-24", + "metadata": {}, + "source": [ + "## Try it yourself\n", + "\n", + "Four things, in rough order of how much you will learn.\n", + "\n", + "Make a file called `random.py` in a directory, put `print(\"not the real one\")` in it, put an empty `main.py` next to it that does `import random`, and run it. Then run it again with `-P`. That is the shadowing problem and its fix in about thirty seconds.\n", + "\n", + "Change the threshold in the import time cell from 300 down to 0 and read the whole list. It is not long, and it is the complete set of things that have to exist before Python can run a single line. Look for `zipimport`, which is there so that the zip file entry from earlier can actually be used.\n", + "\n", + "Add `(\"-X frozen_modules=off\", [\"-X\", \"frozen_modules=off\"])` to the counting cell and watch the frozen count collapse and the from a file count rise. That is you turning a release build into a debug build for one setting, and it is worth doing before reading the last section again.\n", + "\n", + "Set `PYTHONOPTIMIZE=2` in your shell and then try to get back to zero using only command line flags. There is exactly one way and it is not `-O`.\n", + "\n", + "## What you now know\n", + "\n", + "A fresh interpreter has a few dozen modules loaded before your first statement, and on a release build none of them were read from a file. They are either C compiled into the binary or Python bytecode frozen into it.\n", + "\n", + "Startup is two halves on purpose. Core startup runs with no import system and no `sys.path`, which is why configuration errors come out as fatal errors rather than exceptions. Main startup is everything that needs an import system, including working out `sys.path`.\n", + "\n", + "Settings can come from the command line, the environment or an embedder, and the answer is the highest number anybody asked for rather than whichever one is nearest. `-E` is the escape hatch, and it works by discarding the environment rather than by outranking it.\n", + "\n", + "`sys.path` is produced by a Python program that is frozen into the binary and handed eleven C functions to stand in for `os.path`, because the module it would normally use cannot be imported until it has finished. Its output always includes a zip file that usually does not exist.\n", + "\n", + "The front of `sys.path` is added last, after startup is over, and it is the directory of your script or the current directory. That is the whole mechanism behind accidentally shadowing a standard library module, and `-P` turns it off.\n", + "\n", + "A plain start costs tens of milliseconds, of which `site` is a real slice, and a debug build costs about double, mostly because it reads from disk what a release build reads from its own binary.\n", + "\n", + "## What is next\n", + "\n", + "This lesson kept saying \"the runtime\" and \"the interpreter\" as if they were one thing. They are not. There is a runtime, there are interpreters inside it, and there are threads inside those, and each of the three owns a different pile of state. C04 made a second interpreter and never said what one is made of.\n", + "\n", + "R02 opens that up. Where every piece of the state this book has been poking at for seventy lessons actually lives, which of those piles is shared and which is not, and why moving one field from one struct to another is a change worth arguing about." + ] + } + ], + "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 62d1450..aeb53cf 100644 --- a/probes/pyodide/lessons.json +++ b/probes/pyodide/lessons.json @@ -329,7 +329,7 @@ { "name": "c05-18", "status": "ok", - "printed": " the collector is scheduled when generation zero passes 2000\n json.loads built them in 53 ms, collector ran 1 times\n a list display did it in 51 ms, collector ran 89 times\n the two of them built the same number of dicts: True\n" + "printed": " the collector is scheduled when generation zero passes 2000\n json.loads built them in 28 ms, collector ran 1 times\n a list display did it in 24 ms, collector ran 89 times\n the two of them built the same number of dicts: True\n" }, { "name": "c05-22", @@ -622,7 +622,7 @@ { "name": "e04-09", "status": "ok", - "printed": " a plain object at 25556152 mod 4 0 mod 8 0\n a list at 25135512 mod 4 0 mod 8 0\n a dict at 23371568 mod 4 0 mod 8 0\n a string at 23487656 mod 4 0 mod 8 0\n a big integer at 23525224 mod 4 0 mod 8 0\n a float at 24360016 mod 4 0 mod 8 0\n a module at 14795336 mod 4 0 mod 8 0\n None at 3200984 mod 4 0 mod 8 0\n the number 7 at 3253660 mod 4 0 mod 8 4\n" + "printed": " a plain object at 24008608 mod 4 0 mod 8 0\n a list at 23678528 mod 4 0 mod 8 0\n a dict at 22083376 mod 4 0 mod 8 0\n a string at 23454488 mod 4 0 mod 8 0\n a big integer at 23739936 mod 4 0 mod 8 0\n a float at 23426664 mod 4 0 mod 8 0\n a module at 14795336 mod 4 0 mod 8 0\n None at 3200984 mod 4 0 mod 8 0\n the number 7 at 3253660 mod 4 0 mod 8 4\n" }, { "name": "e04-12", @@ -691,12 +691,12 @@ { "name": "e05-18", "status": "ok", - "printed": " no_try 67.5 ns per iteration\n with_try 66.2 ns per iteration\n checking 163.8 ns per iteration\n" + "printed": " no_try 37.9 ns per iteration\n with_try 38.6 ns per iteration\n checking 98.8 ns per iteration\n" }, { "name": "e05-21", "status": "ok", - "printed": " through 1 frames 1208.9 ns\n through 5 frames 1583.2 ns\n through 20 frames 4571.6 ns\n through 50 frames 10780.1 ns\n" + "printed": " through 1 frames 445.1 ns\n through 5 frames 924.8 ns\n through 20 frames 2392.0 ns\n through 50 frames 5680.3 ns\n" }, { "name": "e05-24", @@ -711,7 +711,7 @@ { "name": "e05-29", "status": "ok", - "printed": " for loop table 0 bytes 44.5 ns per item\n while and next table 12 bytes 60.3 ns per item\n" + "printed": " for loop table 0 bytes 23.4 ns per item\n while and next table 12 bytes 33.2 ns per item\n" }, { "name": "e05-32", @@ -780,7 +780,7 @@ { "name": "e06-31", "status": "ok", - "printed": " every value an int 25.1 ns per addition\n every value a float 28.5 ns per addition\n alternating between the two 46.9 ns per addition\n" + "printed": " every value an int 28.8 ns per addition\n every value a float 29.2 ns per addition\n alternating between the two 50.5 ns per addition\n" }, { "name": "e06-34", @@ -834,7 +834,7 @@ { "name": "e07-25", "status": "ok", - "printed": " with the jit on\n this build has no JIT in it, so there is nothing to switch on\n\n with the jit off, for comparison\n arithmetic loop 75.1 ns per iteration\n loop with a call 90.9 ns per iteration\n" + "printed": " with the jit on\n this build has no JIT in it, so there is nothing to switch on\n\n with the jit off, for comparison\n arithmetic loop 42.4 ns per iteration\n loop with a call 49.5 ns per iteration\n" } ] }, @@ -863,7 +863,7 @@ { "name": "e08-11", "status": "ok", - "printed": " nobody watching 55.7 ns per turn\n callback counts the line 392.1 ns per turn, 2000015 calls\n callback returns DISABLE 87.6 ns per turn\n" + "printed": " nobody watching 33.3 ns per turn\n callback counts the line 228.8 ns per turn, 2000015 calls\n callback returns DISABLE 56.7 ns per turn\n" }, { "name": "e08-14", @@ -1020,17 +1020,17 @@ { "name": "e11-15", "status": "ok", - "printed": " extra lines instructions run nanoseconds cost of one more\n 0 7010 43644 \n 5 17010 62891 1.92\n 10 27010 211314 14.84\n 20 47010 320734 5.47\n 40 87010 555924 5.88\n" + "printed": " extra lines instructions run nanoseconds cost of one more\n 0 7010 37579 \n 5 17010 54112 1.65\n 10 27010 195473 14.14\n 20 47010 315875 6.02\n 40 87010 493470 4.44\n" }, { "name": "e11-18", "status": "ok", - "printed": " the same job instructions nanoseconds each\n a while loop 20012 108988 5.45\n a for loop 10008 50037 5.00\n a comprehension 6011 39448 6.56\n" + "printed": " the same job instructions nanoseconds each\n a while loop 20012 99132 4.95\n a for loop 10008 43715 4.37\n a comprehension 6011 35810 5.96\n" }, { "name": "e11-21", "status": "ok", - "printed": " one bytecode instruction 4.91 ns\n one call to an empty def 29.95 ns beyond the code that calls it\n so a call costs about 6 instructions\n" + "printed": " one bytecode instruction 4.35 ns\n one call to an empty def 29.41 ns beyond the code that calls it\n so a call costs about 7 instructions\n" } ] }, @@ -1489,7 +1489,7 @@ { "name": "f10-11", "status": "ok", - "printed": " -- MAKE_CELL 0 (a)\n MAKE_CELL 6 (n)\n\n 1 RESUME 0\n\n 2 LOAD_SMALL_INT 0\n STORE_DEREF 6 (n)\n\n 4 LOAD_FAST_BORROW 0 (a)\n LOAD_FAST_BORROW 6 (n)\n BUILD_TUPLE 2\n LOAD_CONST 1 ()\n MAKE_FUNCTION\n SET_FUNCTION_ATTRIBUTE 8 (closure)\n STORE_FAST 5 (inner)\n\n 7 LOAD_FAST_BORROW 5 (inner)\n RETURN_VALUE\n\nDisassembly of :\n -- COPY_FREE_VARS 2\n\n 4 RESUME 0\n\n 5 LOAD_DEREF 0 (a)\n LOAD_DEREF 1 (n)\n BINARY_OP 0 (+)\n RETURN_VALUE\n" + "printed": " -- MAKE_CELL 0 (a)\n MAKE_CELL 6 (n)\n\n 1 RESUME 0\n\n 2 LOAD_SMALL_INT 0\n STORE_DEREF 6 (n)\n\n 4 LOAD_FAST_BORROW 0 (a)\n LOAD_FAST_BORROW 6 (n)\n BUILD_TUPLE 2\n LOAD_CONST 1 ()\n MAKE_FUNCTION\n SET_FUNCTION_ATTRIBUTE 8 (closure)\n STORE_FAST 5 (inner)\n\n 7 LOAD_FAST_BORROW 5 (inner)\n RETURN_VALUE\n\nDisassembly of :\n -- COPY_FREE_VARS 2\n\n 4 RESUME 0\n\n 5 LOAD_DEREF 0 (a)\n LOAD_DEREF 1 (n)\n BINARY_OP 0 (+)\n RETURN_VALUE\n" }, { "name": "f10-13", @@ -1581,12 +1581,12 @@ { "name": "f12-07", "status": "ok", - "printed": " greet.py is 54 bytes of text\n greet.cpython-314.pyc is 251 bytes\n\n header 2b 0e 0d 0a 00 00 00 00 65 43 9c 6a 36 00 00 00\n the rest 235 bytes, and marshal.loads gives back a\n code object\n" + "printed": " greet.py is 54 bytes of text\n greet.cpython-314.pyc is 251 bytes\n\n header 2b 0e 0d 0a 00 00 00 00 1d 4e 9c 6a 36 00 00 00\n the rest 235 bytes, and marshal.loads gives back a\n code object\n" }, { "name": "f12-10", "status": "ok", - "printed": " magic 168627755\n flags 0\n mtime 1788625765 source 1788625765\n size 54 source 54\n" + "printed": " magic 168627755\n flags 0\n mtime 1788628509 source 1788628509\n size 54 source 54\n" }, { "name": "f12-13", @@ -1620,7 +1620,7 @@ { "name": "f12-29", "status": "ok", - "printed": " one bit flipped in the magic: bad magic number in 'broken': b'*\\x0e\\r\\n'\n\n put a wrong mtime in greet.cpython-314.pyc: 1788625818\n imported it with no complaint: 'hi world'\n and the header now says 1788625818\n the file was quietly rewritten: False\n" + "printed": " one bit flipped in the magic: bad magic number in 'broken': b'*\\x0e\\r\\n'\n\n put a wrong mtime in greet.cpython-314.pyc: 1788628706\n imported it with no complaint: 'hi world'\n and the header now says 1788628706\n the file was quietly rewritten: False\n" } ] }, @@ -1963,7 +1963,7 @@ { "name": "m07-29", "status": "ok", - "printed": " frozen objects before 0\n tracked in generation 2 24934\n frozen after gc.freeze() 24934\n left in generation 2 0\n frozen after gc.unfreeze() 0\n" + "printed": " frozen objects before 0\n tracked in generation 2 24939\n frozen after gc.freeze() 24939\n left in generation 2 0\n frozen after gc.unfreeze() 0\n" } ] }, @@ -1987,7 +1987,7 @@ { "name": "m08-09", "status": "ok", - "printed": " objects in each generation [3, 0, 24920]\n as soon as it exists generations [0]\n after a pass over gen 0 generations [2]\n after a pass over gen 1 generations [2]\n" + "printed": " objects in each generation [3, 0, 24925]\n as soon as it exists generations [0]\n after a pass over gen 0 generations [2]\n after a pass over gen 1 generations [2]\n" }, { "name": "m08-12", @@ -2002,12 +2002,12 @@ { "name": "m08-17", "status": "ok", - "printed": " cycles on the heap 200000\n one full pass over it 63 ms\n and over an empty one 6 ms\n" + "printed": " cycles on the heap 200000\n one full pass over it 19 ms\n and over an empty one 4 ms\n" }, { "name": "m08-20", "status": "ok", - "printed": " this version does not report candidates, so there is nothing to compare\n objects the collector is tracking 24936\n" + "printed": " this version does not report candidates, so there is nothing to compare\n objects the collector is tracking 24941\n" } ] }, @@ -2085,7 +2085,7 @@ { "name": "o01-07", "status": "ok", - "printed": " a pointer here is 4 bytes, so the header is 8\n it lives at 24411248\n\n first word, read as a count 1\n sys.getrefcount, less its own argument 1\n the same number: True\n\n second word 24413920\n id(Thing) is 24413920\n so the second word is the type pointer: True\n\n after a second name, the count reads 2\n after deleting that name, it reads 1\n" + "printed": " a pointer here is 4 bytes, so the header is 8\n it lives at 23220840\n\n first word, read as a count 1\n sys.getrefcount, less its own argument 1\n the same number: True\n\n second word 23930320\n id(Thing) is 23930320\n so the second word is the type pointer: True\n\n after a second name, the count reads 2\n after deleting that name, it reads 1\n" }, { "name": "o01-10", @@ -2820,6 +2820,50 @@ } ] }, + { + "slug": "r01-before-your-first-line", + "cells": [ + { + "name": "r01-03", + "status": "ok" + }, + { + "name": "r01-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": "r01-07", + "status": "ok", + "printed": " this runtime cannot start another interpreter, so there is nothing to look at\n" + }, + { + "name": "r01-10", + "status": "ok", + "printed": " this runtime cannot start another interpreter, so there is nothing to look at\n" + }, + { + "name": "r01-13", + "status": "ok", + "printed": " this runtime cannot start another interpreter, so there is nothing to look at\n" + }, + { + "name": "r01-15", + "status": "ok", + "printed": " this runtime cannot start another interpreter, so there is nothing to look at\n" + }, + { + "name": "r01-18", + "status": "ok", + "printed": " this runtime cannot start another interpreter, so there is nothing to look at\n" + }, + { + "name": "r01-21", + "status": "ok", + "printed": " this runtime cannot start another interpreter, so there is nothing to look at\n" + } + ] + }, { "slug": "t01-one-line-seven-stages", "cells": [ @@ -3202,7 +3246,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", @@ -3479,7 +3523,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", @@ -3513,12 +3557,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 0x1503478, 3 reference(s), 26 bytes, not tracked\nlist at 0x1819300, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x18193b8, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x132b0e8, 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 0x1089300, 3 reference(s), 26 bytes, not tracked\nlist at 0x16d0058, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x174b548, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x14f22b0, 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) 0x1672248\nid(b) 0x1385f28\nid(c) 0x1672248\n" + "printed": "a == b True same contents\na is b False different objects\na is c True same object\n\nid(a) 0x11cb5a0\nid(b) 0x13cee28\nid(c) 0x11cb5a0\n" }, { "name": "t08-17", @@ -3558,7 +3602,7 @@ { "name": "t08-35", "status": "ok", - "printed": "dict at 0x1342e70\ndict at 0x1671198\nlist at 0x14fe408\n" + "printed": "dict at 0x13e8990\ndict at 0x185fc88\nlist at 0x174b030\n" }, { "name": "t08-37", @@ -3617,7 +3661,7 @@ { "name": "t09-21", "status": "ok", - "printed": "Node at 0x16f3d50 -> Node at 0x14fb4f8 -> Node at 0x1885190 -> Node at 0x16f3d50\n" + "printed": "Node at 0x15f9b88 -> Node at 0x16b3ce8 -> Node at 0x14c9c70 -> Node at 0x15f9b88\n" }, { "name": "t09-23", @@ -3647,7 +3691,7 @@ { "name": "t09-37", "status": "ok", - "printed": "first object was at 0x1158008\nsecond object is at 0x1158008\nsame address reused -> True\n" + "printed": "first object was at 0x1353fd0\nsecond object is at 0x1353fd0\nsame address reused -> True\n" } ] }, diff --git a/probes/pyodide/lessons.md b/probes/pyodide/lessons.md index ab0107b..bc5547c 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. -71 lesson(s) on Pyodide 3.14.2: 71 ran end to end, 697 cell(s) in total. +72 lesson(s) on Pyodide 3.14.2: 72 ran end to end, 705 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. @@ -67,6 +67,7 @@ The checks in `report.md` next to this ask whether a surface exists. This runs t | o12-the-objects-that-come-back | 11 | runs end to end | | o13-pointing-without-holding | 10 | runs end to end | | o14-the-last-thing-an-object-does | 9 | runs end to end | +| r01-before-your-first-line | 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 | diff --git a/pyxray/src/pyxray/glossary.py b/pyxray/src/pyxray/glossary.py index aef6ba7..3b6a04f 100644 --- a/pyxray/src/pyxray/glossary.py +++ b/pyxray/src/pyxray/glossary.py @@ -1754,6 +1754,41 @@ def anchor(name: str) -> str: ) +STARTUP = Group( + "Starting up and shutting down", + "The words for what happens before your first line runs and after your last one does. R01 through R09 are the lessons, and most of what they cover has already happened by the time you get a prompt.", + ( + Term( + name="two phase initialisation", + short="Startup split in half, because the first half cannot import anything.", + long="Core initialisation builds the runtime, the main interpreter and the built in types, and it has to work with no import system, no `sys.path` and no encodings. Main initialisation is everything that needs those, including computing `sys.path` itself. The split is why a configuration error can be reported as a fatal error with a plain C string rather than as a Python exception: at that point there is nothing to raise.", + cite="Python/pylifecycle.c:1609-1639@v3.15.0rc1#Py_InitializeFromConfig", + also=("`pyinit_core`", "`pyinit_main`", "`Py_InitializeFromConfig`"), + see=("path configuration", "safe path"), + met="R01", + ), + Term( + name="path configuration", + short="The set of paths worked out at startup, of which sys.path is the visible part.", + long="`sys.prefix`, `sys.exec_prefix`, `sys.executable`, `sys._base_executable` and `sys.path` are all decided together by one program, `Modules/getpath.py`, which is compiled to bytecode at build time and evaluated during main initialisation. It is written in Python and it is not on `sys.path`, because `sys.path` is what it is there to produce, so it is handed eleven C functions to stand in for the `os.path` it cannot import.", + cite="Modules/getpath.c:855-894@v3.15.0rc1#_PyConfig_InitPathConfig", + also=("`Modules/getpath.py`", "`_PyConfig_InitPathConfig`"), + see=("two phase initialisation", "safe path"), + met="R01", + ), + Term( + name="safe path", + short="The switch that stops the script's own directory going on the front of sys.path.", + long="`-P` on the command line or `PYTHONSAFEPATH` in the environment. Without it the interpreter puts the script's directory, or the empty string for `-c` and `-m`, at `sys.path[0]` after everything else is already set up, which is what makes a file called `random.py` next to your script shadow the standard library. With it, that entry is simply not added.", + cite="Modules/main.c:696-728@v3.15.0rc1#safe_path", + also=("`-P`", "`PYTHONSAFEPATH`", "`config->safe_path`"), + see=("path configuration",), + met="R01", + ), + ), +) + + BUILDING = Group( "Building the interpreter", "The words that turn out to be about the binary rather than about the language. B01 through B04 are the lessons, and several numbers in the earlier lessons move when the build does.", @@ -1965,6 +2000,7 @@ def anchor(name: str) -> str: OBJECTS, MEMORY, CONCURRENCY, + STARTUP, BUILDING, DEBUGGING, TESTING, diff --git a/tools/tier1/src/tier1/experiments.py b/tools/tier1/src/tier1/experiments.py index 6b18464..dbc2e29 100644 --- a/tools/tier1/src/tier1/experiments.py +++ b/tools/tier1/src/tier1/experiments.py @@ -2244,6 +2244,124 @@ def best(run, rounds=3): ) +#: The program for both r01 recordings, run once on each build. +PROGRAM_TWENTY = r'''"""What has already happened before your first line runs, and what it cost. + +Nothing here is about your code. Every number is the interpreter getting itself ready: reading a +configuration, building a runtime, creating the main interpreter, importing the modules it cannot +run without, working out where the standard library is, and importing site. + +The module counts come from a child process asking itself what is in sys.modules before it does +anything. The timings are the fastest of twenty runs of a child that does nothing at all, so what +is being measured is startup and only startup. + +Running this on a debug build as well as a release one is the point. The work is identical, the +counts come out the same, and the clock does not, which puts a number on what the assertions and +the reference count bookkeeping cost before your program even begins. +""" + +import subprocess +import sys +import time + +COUNT = """ +import sys +kinds = {"built-in": 0, "frozen": 0, "from a file": 0} +for module in sys.modules.values(): + origin = getattr(getattr(module, "__spec__", None), "origin", None) + if origin in ("built-in", "frozen"): + kinds[origin] += 1 + elif origin is not None: + kinds["from a file"] += 1 +print(len(sys.modules), kinds["built-in"], kinds["frozen"], kinds["from a file"]) +""" + +WAYS = [ + ("everything", []), + ("no site", ["-S"]), + ("isolated and no site", ["-I", "-S"]), +] + + +def child(flags, code): + """Run a fresh interpreter with those flags and hand back what it printed.""" + done = subprocess.run( + [sys.executable, *flags, "-c", code], capture_output=True, text=True, check=True + ) + return done.stdout.strip() + + +def best(flags, rounds=20): + """The fastest of a few runs, which is the honest number on a shared machine.""" + fastest = None + for _ in range(rounds): + started = time.perf_counter() + subprocess.run([sys.executable, *flags, "-c", "pass"], capture_output=True, check=True) + taken = time.perf_counter() - started + fastest = taken if fastest is None else min(fastest, taken) + return fastest + + +def import_cost(): + """Add up the self times that -X importtime prints, in milliseconds.""" + done = subprocess.run( + [sys.executable, "-X", "importtime", "-c", "pass"], capture_output=True, text=True + ) + total = 0 + for line in done.stderr.splitlines(): + parts = line.split("|") + if len(parts) == 3 and parts[0].startswith("import time:"): + head = parts[0].removeprefix("import time:").strip() + if head.isdigit(): + total += int(head) + return total / 1000 + + +print(f"~ this is a debug build: {hasattr(sys, 'gettotalrefcount')}") + +for label, flags in WAYS: + total, builtin, frozen, files = child(flags, COUNT).split() + print(f"modules at the first line, {label}: {total}") + print(f" of those, built into the binary: {builtin}") + print(f" of those, frozen bytecode: {frozen}") + print(f" of those, read from a file on disk: {files}") + +for label, flags in WAYS: + print(f"~ starting up, {label}: {best(flags) * 1000:.1f} ms") + +print(f"~ of that, spent importing: {import_cost():.1f} ms") +''' + + +WHAT_STARTUP_COSTS = Experiment( + slug="r01-what-startup-costs", + lesson="R01", + title="What the interpreter has already done before your first line, on a release build", + asks="How much is already loaded before your program starts, and how long did that take?", + needs=( + "it starts twenty child interpreters and takes the fastest, so it needs a machine that is " + "not doing anything else and an interpreter installed the ordinary way rather than a " + "source tree, because the path search is part of what is being timed" + ), + build="release", + program=PROGRAM_TWENTY, +) + + +WHAT_STARTUP_COSTS_ON_A_DEBUG_BUILD = Experiment( + slug="r01-what-startup-costs-on-a-debug-build", + lesson="R01", + title="The same startup on a build with the assertions left in", + asks="Does a debug build do more work at startup, or the same work more slowly?", + needs=( + "it needs an interpreter configured with --with-pydebug, and it has to come out of the " + "same image pipeline as the release half or the two sets of timings cannot be compared" + ), + build="debug", + program=PROGRAM_TWENTY, +) + + EXPERIMENTS: tuple[Experiment, ...] = ( COMPILING_COSTS_NOTHING_THAT_LASTS, A_LEAK_YOU_CAN_SEE, @@ -2271,6 +2389,8 @@ def best(run, rounds=3): WHAT_ONE_THREAD_PAYS_WITH_THE_LOCK, THREE_WAYS_TO_SPLIT_THE_WORK, THREE_WAYS_WITHOUT_THE_LOCK, + WHAT_STARTUP_COSTS, + WHAT_STARTUP_COSTS_ON_A_DEBUG_BUILD, )