diff --git a/GLOSSARY.md b/GLOSSARY.md index 7f2c391..16c35f0 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -8,7 +8,7 @@ This file is generated from `pyxray/src/pyxray/glossary.py`. Edit that and run ` ## Index -[ASDL](#asdl) | [Argument Clinic](#argument-clinic) | [C3 linearization](#c3-linearization) | [EXTENDED_ARG](#extended_arg) | [GC pre header](#gc-pre-header) | [JIT](#jit) | [MRO](#mro) | [PEG parser](#peg-parser) | [PyVarObject](#pyvarobject) | [Pyodide](#pyodide) | [WebAssembly](#webassembly) | [abstract syntax tree](#abstract-syntax-tree) | [adaptive instruction](#adaptive-instruction) | [arena](#arena) | [assembler](#assembler) | [backtrace](#backtrace) | [basic block](#basic-block) | [binding](#binding) | [block](#block) | [blurb](#blurb) | [borrowed reference](#borrowed-reference) | [bytecode](#bytecode) | [cell](#cell) | [class cell](#class-cell) | [closure](#closure) | [code generation](#code-generation) | [code object](#code-object) | [coding cookie](#coding-cookie) | [cold block](#cold-block) | [computed goto](#computed-goto) | [configure](#configure) | [constant folding](#constant-folding) | [control flow graph](#control-flow-graph) | [cycle collector](#cycle-collector) | [data descriptor](#data-descriptor) | [deallocation](#deallocation) | [debug build](#debug-build) | [deoptimization](#deoptimization) | [devguide](#devguide) | [dispatch](#dispatch) | [environment changed](#environment-changed) | [eval loop](#eval-loop) | [evaluation order](#evaluation-order) | [exception table](#exception-table) | [f string](#f-string) | [finalizer](#finalizer) | [frame](#frame) | [free threaded build](#free-threaded-build) | [free variable](#free-variable) | [gdb](#gdb) | [generated file](#generated-file) | [generation](#generation) | [grammar](#grammar) | [header file](#header-file) | [heap type](#heap-type) | [immortal object](#immortal-object) | [indent and dedent](#indent-and-dedent) | [inline cache](#inline-cache) | [instance dictionary](#instance-dictionary) | [instruction](#instruction) | [interning](#interning) | [left recursion](#left-recursion) | [line table](#line-table) | [magic number](#magic-number) | [marshal](#marshal) | [metaclass](#metaclass) | [monitoring events](#monitoring-events) | [new reference](#new-reference) | [object](#object) | [object header](#object-header) | [obmalloc](#obmalloc) | [oparg](#oparg) | [opcode](#opcode) | [parser generator](#parser-generator) | [pdb](#pdb) | [pointer](#pointer) | [pool](#pool) | [product type](#product-type) | [profile guided optimization](#profile-guided-optimization) | [pseudo instruction](#pseudo-instruction) | [pyc file](#pyc-file) | [pyconfig](#pyconfig) | [reference count](#reference-count) | [reference cycle](#reference-cycle) | [reference leak](#reference-leak) | [regen](#regen) | [regrtest](#regrtest) | [replacement field](#replacement-field) | [resource](#resource) | [scope](#scope) | [segmentation fault](#segmentation-fault) | [short circuiting](#short-circuiting) | [slot](#slot) | [slot wrapper](#slot-wrapper) | [small integer cache](#small-integer-cache) | [soft keyword](#soft-keyword) | [specialization](#specialization) | [stack depth](#stack-depth) | [stack effect](#stack-effect) | [static type](#static-type) | [stolen reference](#stolen-reference) | [struct](#struct) | [sum type](#sum-type) | [symbol table](#symbol-table) | [symbol table pass](#symbol-table-pass) | [t string](#t-string) | [test case](#test-case) | [tier one](#tier-one) | [tier two](#tier-two) | [token](#token) | [tokenizer](#tokenizer) | [trace function](#trace-function) | [type object](#type-object) | [underflow](#underflow) | [value stack](#value-stack) | [weak reference](#weak-reference) +[ASDL](#asdl) | [Argument Clinic](#argument-clinic) | [C3 linearization](#c3-linearization) | [EXTENDED_ARG](#extended_arg) | [GC pre header](#gc-pre-header) | [JIT](#jit) | [MRO](#mro) | [PEG parser](#peg-parser) | [PyVarObject](#pyvarobject) | [Pyodide](#pyodide) | [WebAssembly](#webassembly) | [abstract syntax tree](#abstract-syntax-tree) | [adaptive instruction](#adaptive-instruction) | [arena](#arena) | [assembler](#assembler) | [backtrace](#backtrace) | [basic block](#basic-block) | [binding](#binding) | [block](#block) | [blurb](#blurb) | [borrowed reference](#borrowed-reference) | [bound method](#bound-method) | [bytecode](#bytecode) | [cell](#cell) | [class cell](#class-cell) | [closure](#closure) | [code generation](#code-generation) | [code object](#code-object) | [coding cookie](#coding-cookie) | [cold block](#cold-block) | [computed goto](#computed-goto) | [configure](#configure) | [constant folding](#constant-folding) | [control flow graph](#control-flow-graph) | [cycle collector](#cycle-collector) | [data descriptor](#data-descriptor) | [deallocation](#deallocation) | [debug build](#debug-build) | [deoptimization](#deoptimization) | [descriptor](#descriptor) | [devguide](#devguide) | [dispatch](#dispatch) | [environment changed](#environment-changed) | [eval loop](#eval-loop) | [evaluation order](#evaluation-order) | [exception table](#exception-table) | [f string](#f-string) | [finalizer](#finalizer) | [frame](#frame) | [free threaded build](#free-threaded-build) | [free variable](#free-variable) | [gdb](#gdb) | [generated file](#generated-file) | [generation](#generation) | [grammar](#grammar) | [header file](#header-file) | [heap type](#heap-type) | [immortal object](#immortal-object) | [indent and dedent](#indent-and-dedent) | [inline cache](#inline-cache) | [instance dictionary](#instance-dictionary) | [instruction](#instruction) | [interning](#interning) | [left recursion](#left-recursion) | [line table](#line-table) | [magic number](#magic-number) | [marshal](#marshal) | [metaclass](#metaclass) | [monitoring events](#monitoring-events) | [new reference](#new-reference) | [object](#object) | [object header](#object-header) | [obmalloc](#obmalloc) | [oparg](#oparg) | [opcode](#opcode) | [parser generator](#parser-generator) | [pdb](#pdb) | [pointer](#pointer) | [pool](#pool) | [product type](#product-type) | [profile guided optimization](#profile-guided-optimization) | [pseudo instruction](#pseudo-instruction) | [pyc file](#pyc-file) | [pyconfig](#pyconfig) | [reference count](#reference-count) | [reference cycle](#reference-cycle) | [reference leak](#reference-leak) | [regen](#regen) | [regrtest](#regrtest) | [replacement field](#replacement-field) | [resource](#resource) | [scope](#scope) | [segmentation fault](#segmentation-fault) | [short circuiting](#short-circuiting) | [slot](#slot) | [slot wrapper](#slot-wrapper) | [small integer cache](#small-integer-cache) | [soft keyword](#soft-keyword) | [specialization](#specialization) | [stack depth](#stack-depth) | [stack effect](#stack-effect) | [static type](#static-type) | [stolen reference](#stolen-reference) | [struct](#struct) | [sum type](#sum-type) | [symbol table](#symbol-table) | [symbol table pass](#symbol-table-pass) | [t string](#t-string) | [test case](#test-case) | [tier one](#tier-one) | [tier two](#tier-two) | [token](#token) | [tokenizer](#tokenizer) | [trace function](#trace-function) | [type object](#type-object) | [underflow](#underflow) | [value stack](#value-stack) | [weak reference](#weak-reference) ## Reading the source @@ -658,6 +658,22 @@ When a type written in C is made ready, `add_operators` walks the slot table and Also written `wrapper_descriptor`. First met in O03. See also [slot](#slot), [type object](#type-object). In the source: [`Objects/typeobject.c:12456-12470@v3.15.0rc1#add_operators`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L12456-L12470). +### descriptor + +**An object in a class dict whose type defines __get__, __set__ or __delete__.** + +Reading an attribute that resolves to a descriptor calls `__get__` rather than handing the object back. There is nothing to inherit from and nothing to register, so having the method is the whole qualification. Functions are descriptors, which is where `self` comes from: `func_descr_get` returns the function on a class and a bound method on an instance. `property`, `classmethod`, `staticmethod`, every `__slots__` entry and most attributes defined from C are descriptors too. The protocol only applies to objects found on the type, so a descriptor sitting in an instance dict is an ordinary value. + +Also written descriptor protocol. First met in O06. See also [data descriptor](#data-descriptor), [slot](#slot). In the source: [`Objects/funcobject.c:1264-1270@v3.15.0rc1#func_descr_get`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/funcobject.c#L1264-L1270). + +### bound method + +**A small object holding a function and the instance it was read from.** + +`PyMethod_New` allocates one with two pointers, `im_func` and `im_self`, and calling it inserts the instance as the first argument. That is all `self` is. A fresh one is built on every attribute read, so `obj.method is obj.method` is false, though the two compare equal. The allocation comes off a free list when one is available, and the interpreter specialises the common call shape so that reading and immediately calling a method skips building the object at all. + +First met in O06. See also [descriptor](#descriptor), [type object](#type-object). In the source: [`Objects/classobject.c:64-84@v3.15.0rc1#PyMethod_New`](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/classobject.c#L64-L84). + ### data descriptor **An object on a type that has __set__ or __delete__ as well as __get__.** diff --git a/README.md b/README.md index 80cf6d5..8f1bdbd 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ A lesson can also end with a boss fight, which is a problem the text does not so | O03 | [Dunders and the slots behind them](lessons/o03-dunders-and-slots/o03.ipynb) | Where int.__add__ comes from when nobody wrote it, why setting __repr__ on an instance does nothing and setting it on a class works an hour later, how one __getitem__ makes a class iterable without an __iter__, why a subclass __radd__ beats a base class __add__, and the three steps between defining __eq__ and getting unhashable type | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o03-dunders-and-slots/o03.ipynb) | | O04 | [The order things are found in](lessons/o04-the-order-things-are-found-in/o04.ipynb) | Why a class with two bases carries a flat list instead of searching, the twenty line merge that builds it and matches CPython exactly on classes out of the standard library, the two ways a class statement can fail before the class exists, why super goes somewhere the class it is written in has never heard of, and the two ways the list changes after the fact | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o04-the-order-things-are-found-in/o04.ipynb) | | O05 | [What a dot does](lessons/o05-what-a-dot-does/o05.ipynb) | The four places x.name looks and the fixed order it looks in them, why a property cannot be shadowed by an instance attribute and a method can, why __getattr__ and __getattribute__ are not two versions of the same hook, why a class lookup starts at the metaclass and passes None where the instance would go, and the version tag that lets four thousand cache entries go stale without anybody clearing them | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o05-what-a-dot-does/o05.ipynb) | +| O06 | [What property actually is](lessons/o06-what-property-actually-is/o06.ipynb) | The three method protocol that property, classmethod, staticmethod and the self in every method you have written all turn out to be, the one line of C that decides whether a descriptor beats the instance dict or loses to it, the seven lines that build a bound method, the five descriptor types C code makes because it cannot write a class body, why a descriptor in an instance dict does nothing at all, and the pass over the class dict that tells each descriptor the name it was assigned to | M4 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o06-what-property-actually-is/o06.ipynb) | More are landing in order. [lessons/README.md](lessons/README.md) explains how one is put together and how to run them locally. diff --git a/citations.lock.json b/citations.lock.json index f7fb111..2e64fba 100644 --- a/citations.lock.json +++ b/citations.lock.json @@ -600,6 +600,11 @@ "first_line": "PyNumber_Multiply(PyObject *v, PyObject *w)", "lines": 1 }, + "Objects/classobject.c:64-84@v3.15.0rc1": { + "digest": "bb82668b374833eb", + "first_line": "PyObject *", + "lines": 21 + }, "Objects/clinic/listobject.c.h:1-3@v3.15.0rc1": { "digest": "3e8ec84ffbf9f1e1", "first_line": "/*[clinic input]", @@ -645,6 +650,66 @@ "first_line": "int", "lines": 5 }, + "Objects/descrobject.c:137-160@v3.15.0rc1": { + "digest": "1026e33b396ad845", + "first_line": "static PyObject *", + "lines": 24 + }, + "Objects/descrobject.c:163-181@v3.15.0rc1": { + "digest": "822311e554d7bab8", + "first_line": "member_get(PyObject *self, PyObject *obj, PyObject *type)", + "lines": 19 + }, + "Objects/descrobject.c:1685-1718@v3.15.0rc1": { + "digest": "b15e3a6673c9087b", + "first_line": "property_descr_get(PyObject *self, PyObject *obj, PyObject *type)", + "lines": 34 + }, + "Objects/descrobject.c:1720-1755@v3.15.0rc1": { + "digest": "fe21cc3e01f1ce44", + "first_line": "static int", + "lines": 36 + }, + "Objects/descrobject.c:183-201@v3.15.0rc1": { + "digest": "135f0037da281aac", + "first_line": "static PyObject *", + "lines": 19 + }, + "Objects/descrobject.c:203-214@v3.15.0rc1": { + "digest": "f4d5b429346acb28", + "first_line": "static PyObject *", + "lines": 12 + }, + "Objects/descrobject.c:216-240@v3.15.0rc1": { + "digest": "02bee99884323ef9", + "first_line": "static int", + "lines": 25 + }, + "Objects/descrobject.c:794-828@v3.15.0rc1": { + "digest": "f10efe24da69b92e", + "first_line": "PyTypeObject PyMemberDescr_Type = {", + "lines": 35 + }, + "Objects/descrobject.c:94-130@v3.15.0rc1": { + "digest": "bc66e218dea859cc", + "first_line": "static PyObject *", + "lines": 37 + }, + "Objects/funcobject.c:1264-1270@v3.15.0rc1": { + "digest": "991dc84999f49f93", + "first_line": "func_descr_get(PyObject *func, PyObject *obj, PyObject *type)", + "lines": 7 + }, + "Objects/funcobject.c:1530-1537@v3.15.0rc1": { + "digest": "760d452e18210a9c", + "first_line": "static PyObject *", + "lines": 8 + }, + "Objects/funcobject.c:1794-1799@v3.15.0rc1": { + "digest": "edc1441a5461c263", + "first_line": "static PyObject *", + "lines": 6 + }, "Objects/listobject.c:103-134@v3.15.0rc1": { "digest": "779a7953df231c11", "first_line": "static int", @@ -775,6 +840,11 @@ "first_line": "dict. */", "lines": 10 }, + "Objects/typeobject.c:12274-12308@v3.15.0rc1": { + "digest": "221dabb2a1f5ee99", + "first_line": "static int", + "lines": 35 + }, "Objects/typeobject.c:12456-12470@v3.15.0rc1": { "digest": "b3eb73a5e54ec845", "first_line": "add_operators(PyTypeObject *type)", diff --git a/lessons/CLAIMS.md b/lessons/CLAIMS.md index 346022b..9af969e 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. -281 claims across 33 lessons, 22 of them not observable from Python. +287 claims across 34 lessons, 22 of them not observable from Python. ## B01. Building CPython, and whether you need to @@ -246,6 +246,17 @@ Without it the exception becomes the rule and this goes back to being a book. | the same LOAD_ATTR instruction takes a different specialised form depending on whether the attribute lives in the instance values, in a slot, on a module, or is a method | [`o05-20`](o05-what-a-dot-does/o05.ipynb) | | assigning a property over an attribute the specialised instruction was relying on makes the guard fail, and the instruction goes back to the general form and then settles on a different specialised one | [`o05-22`](o05-what-a-dot-does/o05.ipynb) | +## O06. What property actually is + +| Claim | Proved by | +| --- | --- | +| an object with __get__ and __set__ in a class dict intercepts both reads and writes of that attribute on every instance of the class | [`o06-07`](o06-what-property-actually-is/o06.ipynb) | +| reading a method off an instance calls the function's __get__ and produces a new bound method object each time, while reading it off the class returns the plain function | [`o06-10`](o06-what-property-actually-is/o06.ipynb) | +| classmethod produces a bound method whose __self__ is the class, and the class it binds to is the one the lookup started from rather than the one that defined the method | [`o06-14`](o06-what-property-actually-is/o06.ipynb) | +| attributes defined from C appear as one of five descriptor types, and whether each is a data descriptor is decided by whether its type fills in tp_descr_set | [`o06-20`](o06-what-property-actually-is/o06.ipynb) | +| a descriptor placed in an instance dict is returned as itself, because the lookup that would call __get__ only runs for objects found on the type | [`o06-24`](o06-what-property-actually-is/o06.ipynb) | +| __set_name__ runs once for every entry in the class dict during class creation, and never runs again for later assignments to the class | [`o06-26`](o06-what-property-actually-is/o06.ipynb) | + ## T01. One line, seven stages | Claim | Proved by | diff --git a/lessons/README.md b/lessons/README.md index ee46bc5..6fdbae3 100644 --- a/lessons/README.md +++ b/lessons/README.md @@ -37,6 +37,7 @@ Each lesson is a notebook you can run. There is nothing to install and nothing t | [O03. Dunders and the slots behind them](o03-dunders-and-slots/o03.ipynb) | The table of ninety four rows that connects every dunder name to a C slot, read forwards when you write a class and backwards when a type written in C is made ready, why a dunder set on an instance is ignored and one assigned to a class takes effect at once, the names that fill two slots and give you truthiness and iteration for free, the two names that share one slot and have to argue about who goes first, and the two mechanisms that put a literal None in __hash__ | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o03-dunders-and-slots/o03.ipynb) | | [O04. The order things are found in](o04-the-order-things-are-found-in/o04.ipynb) | The MRO as a list computed once and stored on the type rather than a search done every time, the C3 merge written out in twenty lines of Python and checked against __mro__ for classes from this notebook and from the standard library, the fast path that skips the merge entirely for single inheritance, the two error messages you get when no order exists and when a base is repeated, why super reads the MRO of the instance and not of the class the call was written in, and how assigning __bases__ or writing a metaclass mro changes the answer afterwards | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o04-the-order-things-are-found-in/o04.ipynb) | | [O05. What a dot does](o05-what-a-dot-does/o05.ipynb) | PyObject_GenericGetAttr in the order it actually runs, the one if statement that is the whole of the precedence rule people memorise, the two attribute hooks that differ by five characters and by everything else, the separate function a class lookup goes through and the None it passes in place of an instance, where the did you mean suggestion is really computed, the per interpreter cache keyed on a version number that gets zeroed rather than swept, and the four different instructions the same line of Python turns into once it has run enough times | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o05-what-a-dot-does/o05.ipynb) | +| [O06. What property actually is](o06-what-property-actually-is/o06.ipynb) | Any object in a class dict with a __get__ on its type, and the four features that turn out to be exactly that, three of them under ten lines of C, the single non null pointer that decides the precedence question for every attribute in the language, func_descr_get and the two pointer object that is where self comes from, the classmethod argument that makes alternative constructors inherit, the five descriptor types an extension module gets instead of a class body and which two of them type check before touching memory, the instance dict entry that sits there being ignored, and type_new_set_names walking a copy of the class dict once and never again | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/o06-what-property-actually-is/o06.ipynb) | ## The three programs diff --git a/lessons/o06-what-property-actually-is/build.py b/lessons/o06-what-property-actually-is/build.py new file mode 100644 index 0000000..e69753f --- /dev/null +++ b/lessons/o06-what-property-actually-is/build.py @@ -0,0 +1,487 @@ +#!/usr/bin/env python +"""O06. What property actually is. + +The sixth lesson of the object model part. O05 mentioned data descriptors as the thing that +beats the instance dict and left it there. This one is about what they are. + +There is one protocol, made of three methods plus a fourth that runs at class creation time. +`property`, `classmethod`, `staticmethod`, the bound method you get from `obj.method`, every +`__slots__` entry and most of what a C extension puts on a type are all the same protocol with +different answers. Three of those are under ten lines of C each. + +The lesson writes a descriptor by hand, takes apart the four familiar ones, and then looks at +the five descriptor types C code builds because it cannot write a class body. + +Run this file to regenerate the notebook, or `just build-lessons` to regenerate all of them. +`just lessons` checks that the committed notebook still matches this file. +""" + +from nbbuild import BANNER, Lesson +from nbdiagram import Diagrams + +lesson = Lesson("o06-what-property-actually-is", "o06") +badge = lesson.badge +cite = lesson.cite +term = lesson.term +figure = Diagrams("o06-what-property-actually-is").figure + + +lesson.md(f""" +# O06. What property actually is + +{badge} + +O05 said a data descriptor wins over the instance dict, and that a plain function does not. It never said what a descriptor is. + +Here is the short answer. Put an object in a class dict, give it a `__get__` method, and reading that attribute calls the method instead of handing back the object. + +{figure("four-of-a-kind", "four familiar features, one protocol, and the line counts")} + +That is it. `property` is that. `classmethod` is that. So is `staticmethod`, and so is the `self` in every method you have ever written. Four things people learn as four separate features, and three of them are under ten lines of C. + +This lesson builds one by hand, then reads the four real ones. +""") + + +lesson.md(""" +## About the source references + +Now and then this lesson points at CPython's own source, like this: `Objects/funcobject.c:1264-1270@v3.15.0rc1#func_descr_get`. + +Read it as three parts: the file, the lines, and the release those line numbers belong to. Sometimes there is a fourth part after a `#`, which is the name of the thing those lines are inside. + +Every reference is a link, and every one is checked against the pinned source on each change, so a stale reference fails the build instead of sending you somewhere wrong. You never have to read any of it. The references are there so you can go deeper when you want to, and so you can check that this lesson is not making things up. + +## Setup + +Colab does not come with the small package these lessons use, so the next cell installs it. If you are running this from a checkout of the repository it is already installed and the cell does nothing. +""") + + +lesson.code(""" +import sys + +if sys.version_info < (3, 14): + print("This lesson needs CPython 3.14 or newer.") + print(f"This runtime is {sys.version.split()[0]}, and the cells below will not run on it.") +else: + try: + import pyxray + except ImportError: + %pip install -q "pyxray @ git+https://github.com/tamnd/cpython-internals@main#subdirectory=pyxray" + import pyxray +""") + + +lesson.md(""" +## Which Python is this + +Everything below was checked against the version this cell prints and against 3.14. The two agree on every line of output in this lesson. +""") + + +lesson.code( + """ +import pyxray + +pyxray.show() +""", + differs=BANNER, + quiet=True, +) + + +lesson.md(f""" +## Three methods, and one of them changes everything + +A {term("descriptor", "any object that defines __get__, __set__ or __delete__ and is stored in a class dict")} is any object with `__get__`, `__set__` or `__delete__` on its type. There is no base class to inherit from and no registration step. Having the method is the whole qualification. + +{figure("the-three-methods", "the three methods of the protocol, plus __set_name__")} + +Write one and you can watch the calls go past. + +{lesson.claim("an object with __get__ and __set__ in a class dict intercepts both reads and writes of that attribute on every instance of the class")} +""") + + +lesson.code(""" +class Logged: + \"\"\"Stores in the instance dict like normal, and prints every call on the way through.\"\"\" + + def __set_name__(self, owner, name): + self.name = name + print(f" __set_name__ ran for {name!r} on {owner.__name__}") + + def __get__(self, obj, owner): + if obj is None: + return self + print(f" __get__ called with obj={obj!r} owner={owner.__name__}") + return obj.__dict__[self.name] + + def __set__(self, obj, value): + print(f" __set__ called with value={value!r}") + obj.__dict__[self.name] = value + + +class Point: + x = Logged() + + def __repr__(self): + return "a Point" + + +p = Point() +p.x = 3 +print(f" p.x is {p.x}") +print(f" and the instance dict holds {p.__dict__}") +""") + + +lesson.md(f""" +Notice what the class body did not do. It never called `__set_name__`, and `Logged` never asked what attribute it was assigned to. Something ran it during the class statement, and there is a section on that at the end. + +The other thing to notice is `if obj is None`. `__get__` gets called for `Point.x` as well as for `p.x`, and in the class case there is no instance to pass, so `obj` is `None`. Returning `self` there is the convention, and it is why `Point.x` in a debugger shows you the descriptor rather than blowing up. + +Now the split O05 kept referring to. `Logged` has `__set__`, so it is a {term("data descriptor", "a descriptor whose type fills in tp_descr_set, meaning it has __set__ or __delete__")} and it beats the instance dict. Take `__set__` away and it becomes a non data descriptor, and the instance dict beats it instead. + +{figure("data-or-not", "the one difference between a data and a non data descriptor")} + +The test in C is one line, {cite("Objects/descrobject.c:1028-1032@v3.15.0rc1#PyDescr_IsData")}. It asks whether the descriptor's type has `tp_descr_set` filled in. Not whether the method does anything useful, not whether it is `__set__` or `__delete__`, just whether the slot is non null. That single bit decides the precedence question for every attribute in Python. +""") + + +lesson.md(f""" +## Every function you have ever written is a descriptor + +Start with the one nobody thinks of as a descriptor. A `def` inside a class body makes a plain function object and puts it in the class dict. Nothing else happens at that point. + +Then you write `g.greet()` and get a bound method, with `self` already filled in. That happens because `function` has a `__get__`. + +{figure("a-function-becomes-a-method", "how a plain function turns into a bound method")} + +{lesson.claim("reading a method off an instance calls the function's __get__ and produces a new bound method object each time, while reading it off the class returns the plain function")} +""") + + +lesson.code(""" +class Greeter: + def hello(self): + return "hi" + + def __repr__(self): + return "a Greeter" + + +g = Greeter() +raw = Greeter.__dict__["hello"] +print(f" Greeter.__dict__['hello'] is a {type(raw).__name__}") +print(f" Greeter.hello is that same object {Greeter.hello is raw}") +print(f" g.hello is not {g.hello is raw}") +print(f" g.hello {g.hello}") +print(f" g.hello.__func__ is raw {g.hello.__func__ is raw}") +print(f" g.hello.__self__ is g {g.hello.__self__ is g}") +print(f" raw.__get__(g, Greeter) {raw.__get__(g, Greeter)}") +print(f" raw.__get__(None, Greeter) is raw {raw.__get__(None, Greeter) is raw}") +""") + + +lesson.md(f""" +The C is seven lines, {cite("Objects/funcobject.c:1264-1270@v3.15.0rc1#func_descr_get")}. If `obj` is `NULL` or `None`, hand back the function unchanged. Otherwise call `PyMethod_New`, {cite("Objects/classobject.c:64-84@v3.15.0rc1#PyMethod_New")}, which allocates a small object holding two pointers: the function and the instance. That is the entire mechanism behind `self`. + +There is no `__set__` there, which makes a function a non data descriptor, which is why `g.hello = something_else` works and shadows the method for that one instance. A `property` would refuse. + +The other consequence is that `g.hello` is a fresh object every time. Two reads give you two different bound methods that happen to compare equal. +""") + + +lesson.code(""" +a = g.hello +b = g.hello +print(f" g.hello is g.hello {a is b}") +print(f" g.hello == g.hello {a == b}") +""") + + +lesson.md(f""" +This is why `obj.method` in a hot loop is not free, and it is also why the interpreter has a `LOAD_METHOD` style specialisation that calls the function directly and skips building the object. `PyMethod_New` does pull from a free list when it can, which takes the edge off. O12 is about those free lists. + +## classmethod and staticmethod are a few lines each + +Both are descriptors, both are plain Python level objects you can construct yourself, and both are shorter than you would guess. + +`staticmethod` is six lines, {cite("Objects/funcobject.c:1794-1799@v3.15.0rc1#sm_descr_get")}. Its `__get__` returns the wrapped callable and ignores both arguments. That is the entire feature. It exists to stop the function's own `__get__` from running. + +`classmethod` is eight, {cite("Objects/funcobject.c:1530-1537@v3.15.0rc1#cm_descr_get")}. It calls the same `PyMethod_New` a plain function does, but passes the class instead of the instance. So a classmethod really is a bound method, bound to a class object. + +{lesson.claim("classmethod produces a bound method whose __self__ is the class, and the class it binds to is the one the lookup started from rather than the one that defined the method")} +""") + + +lesson.code(""" +class Tools: + @classmethod + def cm(cls): + return cls + + @staticmethod + def sm(): + return "plain" + + +sm_raw = Tools.__dict__["sm"] +print(f" Tools.cm {Tools.cm}") +print(f" Tools.cm.__self__ is Tools {Tools.cm.__self__ is Tools}") +print(f" Tools().cm.__self__ is Tools {Tools().cm.__self__ is Tools}") +print(f" Tools.sm() {Tools.sm()}") +print(f" staticmethod handed the function straight back {Tools.sm is sm_raw.__wrapped__}") + + +class Sub(Tools): + pass + + +print(f" Sub.cm() is Sub {Sub.cm() is Sub}") +""") + + +lesson.md(f""" +That last line is the useful part. `cm_descr_get` receives whichever class the lookup started from, so `Sub.cm()` gets `Sub` even though the method was defined on `Tools`. Alternative constructors work because of that one argument. + +`Tools().cm` binds to the class too, not the instance. The C takes `type` when it has one and falls back to `Py_TYPE(obj)` when it does not, so calling a classmethod through an instance still gives you the class. + +## property is the one with a setter + +`property` is the longest of the four and still not long. Its `__get__` is {cite("Objects/descrobject.c:1685-1718@v3.15.0rc1#property_descr_get")}, and most of those lines are the error message for a property with no getter. The working part is two lines: return `self` when there is no instance, otherwise call the getter with the instance as its only argument. + +What makes it different from the other three is `property_descr_set`, {cite("Objects/descrobject.c:1720-1755@v3.15.0rc1#property_descr_set")}. Having that filled in is what makes `property` a data descriptor, and it is the only reason a property cannot be shadowed by an instance attribute. +""") + + +lesson.code(""" +class Config: + def __init__(self): + self._value = 1 + + @property + def value(self): + return self._value + + @value.setter + def value(self, new): + self._value = new + + def __repr__(self): + return "a Config" + + +c = Config() +prop = Config.__dict__["value"] +print(f" Config.__dict__['value'] is a {type(prop).__name__}") +print(f" and it has __set__ {hasattr(prop, '__set__')}") +c.value = 5 +print(f" c.value {c.value}") +print(f" c.__dict__ {c.__dict__}") + +c.__dict__["value"] = "a direct write into the instance dict" +print(f" after writing straight into the dict, c.value is still {c.value}") +""") + + +lesson.md(""" +The last two lines are worth sitting with. The instance dict genuinely has a `value` key in it now. Nothing raised, nothing cleaned it up, and the attribute still reads `5`. Attribute lookup checked the type first, found a data descriptor, and never looked at the dict at all. The key is just sitting there being ignored. + +That is the same rule from O05, seen from the other side. + +The error path is worth a look too, since the message tells you exactly which of the three functions was missing. +""") + + +lesson.code(""" +class ReadOnly: + @property + def frozen(self): + return 1 + + +target = ReadOnly() +attempts = [ + ("assign", lambda: setattr(target, "frozen", 2)), + ("delete", lambda: delattr(target, "frozen")), +] +for label, action in attempts: + try: + action() + except AttributeError as error: + print(f" {label} {error}") +""") + + +lesson.md(f""" +Both go through `property_descr_set`. It picks `prop_del` when the value is `NULL` and `prop_set` otherwise, finds neither is set, and formats a message naming the property and the class. The property learned its own name from `__set_name__`, which is the same mechanism `Logged` used at the top. + +## The descriptors C code builds + +C code cannot write a class body. When an extension type declares its attributes it fills in tables of C structs, and CPython turns those into descriptor objects when the type is created. There are five of them and you have been using them all along. + +{figure("the-c-side", "the five descriptor types C code produces, and which are data descriptors")} + +{lesson.claim("attributes defined from C appear as one of five descriptor types, and whether each is a data descriptor is decided by whether its type fills in tp_descr_set")} +""") + + +lesson.code(""" +class Slotted: + __slots__ = ("count",) + + +rows = [ + ("a __slots__ entry", Slotted.__dict__["count"]), + ("int.numerator", int.__dict__["numerator"]), + ("str.join", str.__dict__["join"]), + ("int.__add__", int.__dict__["__add__"]), + ("dict.fromkeys", dict.__dict__["fromkeys"]), +] +for label, descr in rows: + kind = type(descr).__name__ + data = "data" if hasattr(descr, "__set__") else "non data" + print(f" {label:18} {kind:23} {data}") +""") + + +lesson.md(f""" +Five types, one protocol. `member_descriptor` reads a field at a fixed byte offset in the object, {cite("Objects/descrobject.c:163-181@v3.15.0rc1#member_get")}, which is what every `__slots__` entry is and what O03 was really describing. `getset_descriptor` calls a C getter function, {cite("Objects/descrobject.c:183-201@v3.15.0rc1#getset_get")}. `method_descriptor` binds a C function to the instance, {cite("Objects/descrobject.c:137-160@v3.15.0rc1#method_get")}, the C equivalent of `func_descr_get`. `wrapper_descriptor` wraps a slot function so you can call it from Python, {cite("Objects/descrobject.c:203-214@v3.15.0rc1#wrapperdescr_get")}. `classmethod_descriptor` is the C version of `classmethod`, {cite("Objects/descrobject.c:94-130@v3.15.0rc1#classmethod_get")}. + +The two data descriptors in that list, `member_descriptor` and `getset_descriptor`, fill in `tp_descr_set`, {cite("Objects/descrobject.c:794-828@v3.15.0rc1#PyMemberDescr_Type")}. The other three leave it at zero and are non data descriptors, which is exactly why you can assign over `str.join` on a subclass and cannot assign over a slot. + +These are the descriptors that carry the type check people run into. A Python descriptor gets whatever instance it is handed. A C one checks first, {cite("Objects/descrobject.c:216-240@v3.15.0rc1#descr_setcheck")}, because writing an `int` field at a fixed offset in an object that is not an `int` would corrupt memory. +""") + + +lesson.code(""" +try: + Slotted.__dict__["count"].__set__("not a Slotted at all", 1) +except TypeError as error: + print(f" {error}") +""") + + +lesson.md(f""" +## It only counts on the type + +One rule that catches people. The protocol is only consulted for objects found on the type. Put a descriptor in an instance dict and it is an ordinary value. + +{lesson.claim("a descriptor placed in an instance dict is returned as itself, because the lookup that would call __get__ only runs for objects found on the type")} +""") + + +lesson.code(""" +class Sneaky: + def __get__(self, obj, owner): + return "the descriptor fired" + + +class Plain: + pass + + +holder = Plain() +holder.from_the_instance = Sneaky() +Plain.from_the_class = Sneaky() + +print(f" holder.from_the_instance {type(holder.from_the_instance).__name__}") +print(f" holder.from_the_class {holder.from_the_class}") +print(f" Plain.from_the_class {Plain.from_the_class}") +""") + + +lesson.md(f""" +Look back at O05's four steps and the reason is obvious. The instance dict branch reads the value and returns it, {cite("Objects/object.c:1887-1926@v3.15.0rc1#_PyObject_GenericGetAttrWithDict")}. Only the MRO branch checks for `__get__`. + +That is not an oversight. If the instance dict were checked too, storing a function on an instance would silently turn it into a method, and every dict value would have to be tested for a descriptor slot before being handed back. + +## How a descriptor learns its own name + +Back to the loose end. `Logged` printed the attribute it was assigned to without being told, because `type` calls `__set_name__` on every value in the class dict on the way to building the class. + +{figure("set-name-timing", "when __set_name__ runs during class creation")} + +The function is `type_new_set_names`, {cite("Objects/typeobject.c:12274-12308@v3.15.0rc1#type_new_set_names")}. It copies the class dict, walks the copy, looks up `__set_name__` on each value, and calls it with the class and the key. The copy matters. It is why a descriptor can add more attributes to the class from inside `__set_name__` without the loop tripping over them. + +{lesson.claim("__set_name__ runs once for every entry in the class dict during class creation, and never runs again for later assignments to the class")} +""") + + +lesson.code(""" +print(" about to run the class statement") + + +class Watched: + first = Logged() + second = Logged() + + +print(" class statement finished, and both had already run") + +Watched.third = Logged() +print(" assigning a descriptor to the class afterwards printed nothing") +print(f" so Watched.__dict__['third'] has no name: {hasattr(Watched.__dict__['third'], 'name')}") +""") + + +lesson.md(""" +The last line is the trap. Adding a descriptor to a class after the class statement gets you a descriptor that never learned its name, and it will fail the first time something reads it. Decorators that rewrite classes have to call `__set_name__` themselves, and `dataclasses` is one of the places in the standard library that does. + +Errors get a note rather than being swallowed. If `__set_name__` raises, the class statement fails and the message tells you which descriptor and which attribute. +""") + + +lesson.code(""" +class Fussy: + def __set_name__(self, owner, name): + raise ValueError("this name will not do") + + +try: + + class Doomed: + field = Fussy() + +except ValueError as error: + print(f" the error {error}") + for note in error.__notes__: + print(f" the note {note}") +""") + + +lesson.md(""" +## Try it yourself + +Three things to poke at. + +Write a caching descriptor. `__get__` computes a value, writes it into `obj.__dict__` under its own name, and returns it. Leave `__set__` off. The second read never reaches the descriptor at all, because a non data descriptor loses to the instance dict, and you get caching for free with no cache lookup. Then add an empty `__set__` and watch the caching stop working. + +Take `property` apart and put it back together in Python. A class holding three functions, a `__get__` that calls the first, a `__set__` that calls the second or third, and a `__set_name__` that remembers the name for the error messages. Under twenty lines, and it will behave the same as the real one for everything in this notebook. + +Find the descriptors on a type you use. `[n for n, v in vars(dict).items() if hasattr(type(v), "__get__")]` will list them, and the interesting part is how few things on a type are not descriptors. + +## What just happened + +A descriptor is any object in a class dict whose type has `__get__`, `__set__` or `__delete__`. There is nothing to inherit from and nothing to register. Reading such an attribute calls `__get__` instead of returning the object. + +Whether it also has `__set__` or `__delete__` decides everything about precedence. The check is one line of C that asks whether `tp_descr_set` is non null. With it, the descriptor beats the instance dict and cannot be shadowed. Without it, the instance dict wins. + +Functions are descriptors, which is where `self` comes from. `func_descr_get` is seven lines and hands back either the function or a new two pointer object holding the function and the instance. `staticmethod` is six lines that hand the callable back unchanged. `classmethod` is eight that bind to the class the lookup started from, which is what makes alternative constructors inherit properly. `property` is the long one at about thirty lines, and the extra length is error messages. + +C code builds five descriptor types because it cannot write a class body. Two of them, the ones behind `__slots__` and behind read only attributes like `int.numerator`, are data descriptors. Those two also type check their instance before touching memory. + +The protocol only applies to objects found on the type. In an instance dict a descriptor is just a value. + +`__set_name__` runs once during class creation, over a copy of the class dict, and never runs again. A descriptor assigned to a class afterwards never learns its name. + +## What is next + +O07 is the dict. Every lesson so far has said "look in the instance dict" or "look in the class dict" as if that were a single simple step, and it is the most tuned data structure in the interpreter. It is a hash table with open addressing, it keeps insertion order without spending anything on it, and it has two completely different memory layouts depending on how it was made. O08 is about the second of those layouts, which is the one that makes ordinary objects small. +""") + + +raise SystemExit(lesson.save()) diff --git a/lessons/o06-what-property-actually-is/diagrams.py b/lessons/o06-what-property-actually-is/diagrams.py new file mode 100644 index 0000000..2cfc4e0 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +"""The diagrams for O06, the descriptor protocol. + +Each scene is written out twice, as an editable `.excalidraw` and as the `.svg` the lesson +embeds. Run this file to regenerate them, or `just build-diagrams` for every lesson. + +The one carrying the lesson is `four-of-a-kind`. People learn `property`, `classmethod`, +`staticmethod` and bound methods as four separate features, and they are one protocol with +four different answers to the same question. +""" + +from nbdiagram import Gallery, figures + +gallery = Gallery("o06-what-property-actually-is") + +gallery.add( + figures.table( + "four-of-a-kind", + ["what you wrote", "what __get__ hands back", "lines of C"], + [ + ["a plain def", "a bound method holding self", "7"], + ["@staticmethod", "the function, untouched", "6"], + ["@classmethod", "a bound method holding the class", "8"], + ["@property", "the result of calling your getter", "34"], + ], + title="Four features you learned separately, one protocol underneath", + caption="All four sit in the class dict and all four answer the same call, __get__.", + tones=["quiet", "quiet", "quiet", "focus"], + ) +) + + +gallery.add( + figures.stack( + "the-three-methods", + [ + "__get__(self, obj, owner) runs when something reads the attribute", + "__set__(self, obj, value) runs when something assigns to it", + "__delete__(self, obj) runs on del", + "__set_name__(self, owner, name) runs once, when the class is built", + ], + title="The whole protocol, and there is nothing else to it", + note="Define __get__ only and you have a non data descriptor. Add __set__ and the " + "precedence flips.", + ) +) + + +gallery.add( + figures.compare( + "data-or-not", + ( + "non data descriptor", + [ + "has __get__ and nothing else", + "the instance dict beats it", + "so you can shadow a method", + "plain functions live here", + ], + ), + ( + "data descriptor", + [ + "has __set__ or __delete__ too", + "it beats the instance dict", + "so you cannot shadow a property", + "property and __slots__ live here", + ], + ), + title="One bit of difference, and it decides who wins", + verdict="PyDescr_IsData is one line. It asks whether tp_descr_set is filled in.", + verdict_tone="focus", + ) +) + + +gallery.add( + figures.flow( + "a-function-becomes-a-method", + [ + "def greet(self) in the class body", + "it lands in the class dict as a plain function", + "g.greet reads it, finds __get__, calls it", + "func_descr_get sees a real obj, not None", + "PyMethod_New packs the function and g together", + ], + title="Where the self in your methods comes from", + labels=[ + "nothing special has happened yet", + "functions are non data descriptors", + "on a class it returns the function unchanged", + ], + tones=["input", "quiet", "focus", "focus", "durable"], + ) +) + + +gallery.add( + figures.table( + "the-c-side", + ["where you find it", "the type you get", "data descriptor"], + [ + ["a __slots__ entry", "member_descriptor", "yes"], + ["int.numerator", "getset_descriptor", "yes"], + ["str.join", "method_descriptor", "no"], + ["int.__add__", "wrapper_descriptor", "no"], + ["dict.fromkeys", "classmethod_descriptor", "no"], + ], + title="C code cannot write a class body, so it builds these instead", + caption="Same protocol, same __get__ call. Only the constructor is different.", + tones=["focus", "focus", "quiet", "quiet", "quiet"], + ) +) + + +gallery.add( + figures.flow( + "set-name-timing", + [ + "the class body runs and fills a dict", + "type() builds the class from that dict", + "type_new_set_names copies the dict and walks it", + "every value with __set_name__ gets called", + "and it never runs again", + ], + title="How a descriptor learns the name it was assigned to", + labels=[ + "your descriptor exists but has no name yet", + "the copy is why you can add attributes inside __set_name__", + "assign the same descriptor later and it stays nameless", + ], + tones=["input", "quiet", "focus", "focus", "durable"], + ) +) + + +raise SystemExit(gallery.save()) diff --git a/lessons/o06-what-property-actually-is/diagrams/a-function-becomes-a-method.excalidraw b/lessons/o06-what-property-actually-is/diagrams/a-function-becomes-a-method.excalidraw new file mode 100644 index 0000000..78ef792 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/a-function-becomes-a-method.excalidraw @@ -0,0 +1,746 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "848144f8c2bc8671a079", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 576.36, + "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": "Where the self in your methods comes from", + "originalText": "Where the self in your methods comes from", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "ec0323eb4a05cdaf03a7", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 562.3999999999999, + "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": "be330d479cc8841b55b4", + "type": "text" + }, + { + "id": "f2e0fe755b01592e97a7", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "be330d479cc8841b55b4", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 530.3999999999999, + "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": "def greet(self) in the class body", + "originalText": "def greet(self) in the class body", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ec0323eb4a05cdaf03a7", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "41a38d4d77a0b32d9631", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 562.3999999999999, + "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": "c1abec12947f824157d3", + "type": "text" + }, + { + "id": "f2e0fe755b01592e97a7", + "type": "arrow" + }, + { + "id": "57115c04dd8572b0cfe4", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "c1abec12947f824157d3", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 530.3999999999999, + "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": "it lands in the class dict as a plain function", + "originalText": "it lands in the class dict as a plain function", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "41a38d4d77a0b32d9631", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "b1adbd43ea42dba3364a", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 562.3999999999999, + "height": 70.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "9b1b7fb2975463f4958f", + "type": "text" + }, + { + "id": "57115c04dd8572b0cfe4", + "type": "arrow" + }, + { + "id": "f05d322e5f528f8f9c7f", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "9b1b7fb2975463f4958f", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 530.3999999999999, + "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": "g.greet reads it, finds __get__, calls it", + "originalText": "g.greet reads it, finds __get__, calls it", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "b1adbd43ea42dba3364a", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "81dda297db74f75c13cc", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 562.3999999999999, + "height": 70.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "bae5e76671982275c65c", + "type": "text" + }, + { + "id": "f05d322e5f528f8f9c7f", + "type": "arrow" + }, + { + "id": "4e1b31ef4b44ff41ac97", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "bae5e76671982275c65c", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 530.3999999999999, + "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": "func_descr_get sees a real obj, not None", + "originalText": "func_descr_get sees a real obj, not None", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "81dda297db74f75c13cc", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "18abd3df592f86fd80c0", + "type": "rectangle", + "x": 0.0, + "y": 570.0, + "width": 562.3999999999999, + "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": "28e77171301cb6f66b9c", + "type": "text" + }, + { + "id": "4e1b31ef4b44ff41ac97", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "28e77171301cb6f66b9c", + "type": "text", + "x": 16.0, + "y": 592.5, + "width": 530.3999999999999, + "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": "PyMethod_New packs the function and g together", + "originalText": "PyMethod_New packs the function and g together", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "18abd3df592f86fd80c0", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "f2e0fe755b01592e97a7", + "type": "arrow", + "x": 281.19999999999993, + "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": "ec0323eb4a05cdaf03a7", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "41a38d4d77a0b32d9631", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "8a9dc99ba85e2aaee9d4", + "type": "text", + "x": 293.19999999999993, + "y": 140.0, + "width": 289.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "nothing special has happened yet", + "originalText": "nothing special has happened yet", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "57115c04dd8572b0cfe4", + "type": "arrow", + "x": 281.19999999999993, + "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": "41a38d4d77a0b32d9631", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "b1adbd43ea42dba3364a", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "500c69b5ec53dc0fbe50", + "type": "text", + "x": 293.19999999999993, + "y": 270.0, + "width": 294.0, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "functions are non data descriptors", + "originalText": "functions are non data descriptors", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f05d322e5f528f8f9c7f", + "type": "arrow", + "x": 281.19999999999993, + "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": "b1adbd43ea42dba3364a", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "81dda297db74f75c13cc", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "36f7ea28d4f6db3c9955", + "type": "text", + "x": 293.19999999999993, + "y": 400.0, + "width": 386.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "on a class it returns the function unchanged", + "originalText": "on a class it returns the function unchanged", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "4e1b31ef4b44ff41ac97", + "type": "arrow", + "x": 281.19999999999993, + "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": "81dda297db74f75c13cc", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "18abd3df592f86fd80c0", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o06-what-property-actually-is/diagrams/a-function-becomes-a-method.svg b/lessons/o06-what-property-actually-is/diagrams/a-function-becomes-a-method.svg new file mode 100644 index 0000000..05c256f --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/a-function-becomes-a-method.svg @@ -0,0 +1 @@ +Where the self in your methods comes fromdef greet(self) in the class bodyit lands in the class dict as a plain functiong.greet reads it, finds __get__, calls itfunc_descr_get sees a real obj, not NonePyMethod_New packs the function and g togethernothing special has happened yetfunctions are non data descriptorson a class it returns the function unchanged diff --git a/lessons/o06-what-property-actually-is/diagrams/data-or-not.excalidraw b/lessons/o06-what-property-actually-is/diagrams/data-or-not.excalidraw new file mode 100644 index 0000000..7554ff0 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/data-or-not.excalidraw @@ -0,0 +1,753 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "e468a091cd42526538d9", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 614.8799999999999, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "One bit of difference, and it decides who wins", + "originalText": "One bit of difference, and it decides who wins", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f00a4db77160986566e5", + "type": "text", + "x": 105.25, + "y": 50.0, + "width": 205.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, + "customData": null, + "text": "non data descriptor", + "originalText": "non data descriptor", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "5f15fe1d0aad5775bb4a", + "type": "rectangle", + "x": 0.0, + "y": 87.0, + "width": 416.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "0a06b9010721e24fa8ab", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "0a06b9010721e24fa8ab", + "type": "text", + "x": 16.0, + "y": 103.0, + "width": 384.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": "has __get__ and nothing else", + "originalText": "has __get__ and nothing else", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "5f15fe1d0aad5775bb4a", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "a0d16c00137a2e776353", + "type": "rectangle", + "x": 0.0, + "y": 147.0, + "width": 416.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "3efee26a6ef7ea6ddbe6", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "3efee26a6ef7ea6ddbe6", + "type": "text", + "x": 16.0, + "y": 163.0, + "width": 384.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "the instance dict beats it", + "originalText": "the instance dict beats it", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "a0d16c00137a2e776353", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "20dce90dbf54b8885292", + "type": "rectangle", + "x": 0.0, + "y": 207.0, + "width": 416.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "1f7b3da8991087a4a2c7", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "1f7b3da8991087a4a2c7", + "type": "text", + "x": 16.0, + "y": 223.0, + "width": 384.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": "so you can shadow a method", + "originalText": "so you can shadow a method", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "20dce90dbf54b8885292", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "d216e1b6d92946cd765c", + "type": "rectangle", + "x": 0.0, + "y": 267.0, + "width": 416.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "6d6a17b7b7d7582b518c", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "6d6a17b7b7d7582b518c", + "type": "text", + "x": 16.0, + "y": 283.0, + "width": 384.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": "plain functions live here", + "originalText": "plain functions live here", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "d216e1b6d92946cd765c", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "a12f8ca1eec4cb558ffa", + "type": "text", + "x": 605.25, + "y": 50.0, + "width": 157.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, + "customData": null, + "text": "data descriptor", + "originalText": "data descriptor", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d4db858a61d5ada3aac2", + "type": "rectangle", + "x": 476.0, + "y": 87.0, + "width": 416.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "794509ee5827248635c8", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "794509ee5827248635c8", + "type": "text", + "x": 492.0, + "y": 103.0, + "width": 384.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": "has __set__ or __delete__ too", + "originalText": "has __set__ or __delete__ too", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "d4db858a61d5ada3aac2", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c131db60c9f16c340c1d", + "type": "rectangle", + "x": 476.0, + "y": 147.0, + "width": 416.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "285e953639a8d20d0dad", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "285e953639a8d20d0dad", + "type": "text", + "x": 492.0, + "y": 163.0, + "width": 384.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": "it beats the instance dict", + "originalText": "it beats the instance dict", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "c131db60c9f16c340c1d", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "9ab34ead471f74c49b15", + "type": "rectangle", + "x": 476.0, + "y": 207.0, + "width": 416.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "fc6242c018b13cb524fa", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "fc6242c018b13cb524fa", + "type": "text", + "x": 492.0, + "y": 223.0, + "width": 384.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": "so you cannot shadow a property", + "originalText": "so you cannot shadow a property", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "9ab34ead471f74c49b15", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "fb1430fd85b4ea748842", + "type": "rectangle", + "x": 476.0, + "y": 267.0, + "width": 416.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "b1929bf8f3982a674043", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "b1929bf8f3982a674043", + "type": "text", + "x": 492.0, + "y": 283.0, + "width": 384.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": "property and __slots__ live here", + "originalText": "property and __slots__ live here", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "fb1430fd85b4ea748842", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "f9c2419b56ef31985be6", + "type": "rectangle", + "x": 0.0, + "y": 344.0, + "width": 892.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "f76f0f1a33384c97acbc", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "f76f0f1a33384c97acbc", + "type": "text", + "x": 16.0, + "y": 361.5, + "width": 860.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": "PyDescr_IsData is one line. It asks whether tp_descr_set is filled in.", + "originalText": "PyDescr_IsData is one line. It asks whether tp_descr_set is filled in.", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "f9c2419b56ef31985be6", + "lineHeight": 1.25, + "autoResize": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o06-what-property-actually-is/diagrams/data-or-not.svg b/lessons/o06-what-property-actually-is/diagrams/data-or-not.svg new file mode 100644 index 0000000..0d0cc36 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/data-or-not.svg @@ -0,0 +1 @@ +One bit of difference, and it decides who winsnon data descriptorhas __get__ and nothing elsethe instance dict beats itso you can shadow a methodplain functions live heredata descriptorhas __set__ or __delete__ tooit beats the instance dictso you cannot shadow a propertyproperty and __slots__ live herePyDescr_IsData is one line. It asks whether tp_descr_set is filled in. diff --git a/lessons/o06-what-property-actually-is/diagrams/four-of-a-kind.excalidraw b/lessons/o06-what-property-actually-is/diagrams/four-of-a-kind.excalidraw new file mode 100644 index 0000000..89444e7 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/four-of-a-kind.excalidraw @@ -0,0 +1,1035 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "b16aca8ca93c67e9184f", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 797.3999999999999, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "Four features you learned separately, one protocol underneath", + "originalText": "Four features you learned separately, one protocol underneath", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e0756d6f290bdae0d07f", + "type": "text", + "x": 14.0, + "y": 50.0, + "width": 135.12, + "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 wrote", + "originalText": "what you wrote", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "41cf4f75dedd9bf35d00", + "type": "text", + "x": 176.4, + "y": 50.0, + "width": 219.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 __get__ hands back", + "originalText": "what __get__ hands back", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "0554238f050c621bdf96", + "type": "text", + "x": 521.2, + "y": 50.0, + "width": 85.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "lines of C", + "originalText": "lines of C", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "0ecf8596c23cb9ff102f", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 631.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 + ], + [ + 631.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "06a91a84bf5c4084b319", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 631.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 + ], + [ + 631.2, + 0.0 + ], + [ + 631.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "3dba6458d1d40d273f2b", + "type": "text", + "x": 14.0, + "y": 86.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": "a plain def", + "originalText": "a plain def", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a22816d259fedc74af47", + "type": "text", + "x": 176.4, + "y": 86.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": "a bound method holding self", + "originalText": "a bound method holding self", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "34a470db5558b481990d", + "type": "text", + "x": 521.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": "7", + "originalText": "7", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e236c8ea229bcf9fa4d1", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 631.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 + ], + [ + 631.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "c412693f3466bb9e0bb2", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 631.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 + ], + [ + 631.2, + 0.0 + ], + [ + 631.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "2424e6f2f97c7a7e7dc8", + "type": "text", + "x": 14.0, + "y": 126.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": "@staticmethod", + "originalText": "@staticmethod", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "1e1fdd5e4e9572d07fe9", + "type": "text", + "x": 176.4, + "y": 126.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": "the function, untouched", + "originalText": "the function, untouched", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a92335fe38b23c648277", + "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": "6", + "originalText": "6", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "20712e0a17f215a98281", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 631.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 + ], + [ + 631.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "2f7f5ae121067c37e24c", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 631.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 + ], + [ + 631.2, + 0.0 + ], + [ + 631.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "b080017ee5a4991d167f", + "type": "text", + "x": 14.0, + "y": 166.0, + "width": 115.19999999999999, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "@classmethod", + "originalText": "@classmethod", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "39e97717c1e572db5338", + "type": "text", + "x": 176.4, + "y": 166.0, + "width": 307.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a bound method holding the class", + "originalText": "a bound method holding the class", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "c8b688e1230fe439cf49", + "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": "8", + "originalText": "8", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "ed17df18d865c6148a03", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 631.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 + ], + [ + 631.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "3e6437d21a4020e21535", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 631.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 + ], + [ + 631.2, + 0.0 + ], + [ + 631.2, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "e14412bdab1a151f91fa", + "type": "text", + "x": 14.0, + "y": 206.0, + "width": 86.39999999999999, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "@property", + "originalText": "@property", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7cce0c71b1a0a31f3f4b", + "type": "text", + "x": 176.4, + "y": 206.0, + "width": 316.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": "the result of calling your getter", + "originalText": "the result of calling your getter", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "51941a61d20e2cfaee54", + "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": "34", + "originalText": "34", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "be70799ae8983abac9c9", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 631.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 + ], + [ + 631.2, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "b5d59812004df1098b0e", + "type": "text", + "x": 0.0, + "y": 256.0, + "width": 642.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": "All four sit in the class dict and all four answer the same call, __get__.", + "originalText": "All four sit in the class dict and all four answer the same call, __get__.", + "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/o06-what-property-actually-is/diagrams/four-of-a-kind.svg b/lessons/o06-what-property-actually-is/diagrams/four-of-a-kind.svg new file mode 100644 index 0000000..d76cdb2 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/four-of-a-kind.svg @@ -0,0 +1 @@ +Four features you learned separately, one protocol underneathwhat you wrotewhat __get__ hands backlines of Ca plain defa bound method holding self7@staticmethodthe function, untouched6@classmethoda bound method holding the class8@propertythe result of calling your getter34All four sit in the class dict and all four answer the same call, __get__. diff --git a/lessons/o06-what-property-actually-is/diagrams/set-name-timing.excalidraw b/lessons/o06-what-property-actually-is/diagrams/set-name-timing.excalidraw new file mode 100644 index 0000000..dc72475 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/set-name-timing.excalidraw @@ -0,0 +1,746 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "347207930efde2af3112", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 700.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": "How a descriptor learns the name it was assigned to", + "originalText": "How a descriptor learns the name it was assigned to", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "9066fe8f71d20ea0c836", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 577.1, + "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": "bdd62f45610a7f0604cc", + "type": "text" + }, + { + "id": "ba3cb27aa801935d6f14", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "bdd62f45610a7f0604cc", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 545.1, + "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 class body runs and fills a dict", + "originalText": "the class body runs and fills a dict", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "9066fe8f71d20ea0c836", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "bfbbeca2f424ef8759b5", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 577.1, + "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": "24dbf45df86d19625239", + "type": "text" + }, + { + "id": "ba3cb27aa801935d6f14", + "type": "arrow" + }, + { + "id": "1a7b7d9193304f41cb92", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "24dbf45df86d19625239", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 545.1, + "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": "type() builds the class from that dict", + "originalText": "type() builds the class from that dict", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "bfbbeca2f424ef8759b5", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "cb00483dbdf6a3e8fd35", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 577.1, + "height": 70.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "3281fd46902d915c9c0d", + "type": "text" + }, + { + "id": "1a7b7d9193304f41cb92", + "type": "arrow" + }, + { + "id": "3db9592dee98a20e206e", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "3281fd46902d915c9c0d", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 545.1, + "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": "type_new_set_names copies the dict and walks it", + "originalText": "type_new_set_names copies the dict and walks it", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "cb00483dbdf6a3e8fd35", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "4f493094ed3f69c2a3af", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 577.1, + "height": 70.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "8b0532f07e632ffc6d4d", + "type": "text" + }, + { + "id": "3db9592dee98a20e206e", + "type": "arrow" + }, + { + "id": "0e4e8332d9580b9ccdcd", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "8b0532f07e632ffc6d4d", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 545.1, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "every value with __set_name__ gets called", + "originalText": "every value with __set_name__ gets called", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "4f493094ed3f69c2a3af", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c2a32c7c27836c6e8fb0", + "type": "rectangle", + "x": 0.0, + "y": 570.0, + "width": 577.1, + "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": "7e17b4d3ef9c597f3444", + "type": "text" + }, + { + "id": "0e4e8332d9580b9ccdcd", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "7e17b4d3ef9c597f3444", + "type": "text", + "x": 16.0, + "y": 592.5, + "width": 545.1, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "and it never runs again", + "originalText": "and it never runs again", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c2a32c7c27836c6e8fb0", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "ba3cb27aa801935d6f14", + "type": "arrow", + "x": 288.55, + "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": "9066fe8f71d20ea0c836", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "bfbbeca2f424ef8759b5", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "06f3e69bf8e2cdda36a3", + "type": "text", + "x": 300.55, + "y": 140.0, + "width": 376.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": "your descriptor exists but has no name yet", + "originalText": "your descriptor exists but has no name yet", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "1a7b7d9193304f41cb92", + "type": "arrow", + "x": 288.55, + "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": "bfbbeca2f424ef8759b5", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "cb00483dbdf6a3e8fd35", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "ab01450a544aba9d8d6c", + "type": "text", + "x": 300.55, + "y": 270.0, + "width": 532.3199999999999, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the copy is why you can add attributes inside __set_name__", + "originalText": "the copy is why you can add attributes inside __set_name__", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "3db9592dee98a20e206e", + "type": "arrow", + "x": 288.55, + "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": "cb00483dbdf6a3e8fd35", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "4f493094ed3f69c2a3af", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "57a7764ae377d4e54796", + "type": "text", + "x": 300.55, + "y": 400.0, + "width": 483.12, + "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": "assign the same descriptor later and it stays nameless", + "originalText": "assign the same descriptor later and it stays nameless", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "0e4e8332d9580b9ccdcd", + "type": "arrow", + "x": 288.55, + "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": "4f493094ed3f69c2a3af", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "c2a32c7c27836c6e8fb0", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/o06-what-property-actually-is/diagrams/set-name-timing.svg b/lessons/o06-what-property-actually-is/diagrams/set-name-timing.svg new file mode 100644 index 0000000..72fa693 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/set-name-timing.svg @@ -0,0 +1 @@ +How a descriptor learns the name it was assigned tothe class body runs and fills a dicttype() builds the class from that dicttype_new_set_names copies the dict and walks itevery value with __set_name__ gets calledand it never runs againyour descriptor exists but has no name yetthe copy is why you can add attributes inside __set_name__assign the same descriptor later and it stays nameless diff --git a/lessons/o06-what-property-actually-is/diagrams/the-c-side.excalidraw b/lessons/o06-what-property-actually-is/diagrams/the-c-side.excalidraw new file mode 100644 index 0000000..bacae74 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/the-c-side.excalidraw @@ -0,0 +1,1235 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "562595a1973c2749ae41", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 802.4399999999998, + "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": "C code cannot write a class body, so it builds these instead", + "originalText": "C code cannot write a class body, so it builds these instead", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "72ec8f271c275163d220", + "type": "text", + "x": 14.0, + "y": 50.0, + "width": 150.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": "where you find it", + "originalText": "where you find it", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "36bbe2a36ee4a5d411a8", + "type": "text", + "x": 205.2, + "y": 50.0, + "width": 142.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": "the type you get", + "originalText": "the type you get", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "aa15633e36c004ffbd4c", + "type": "text", + "x": 444.4, + "y": 50.0, + "width": 126.0, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "data descriptor", + "originalText": "data descriptor", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "0d2779f400650d029574", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "7a397c9e5b22bd07faa8", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ], + [ + 602.4, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "83f9e03a872f18e58692", + "type": "text", + "x": 14.0, + "y": 86.0, + "width": 163.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a __slots__ entry", + "originalText": "a __slots__ entry", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "3eeb3b8d3ff5f88b0c03", + "type": "text", + "x": 205.2, + "y": 86.0, + "width": 163.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "member_descriptor", + "originalText": "member_descriptor", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "200fc813c3d2e83bae61", + "type": "text", + "x": 444.4, + "y": 86.0, + "width": 28.799999999999997, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "yes", + "originalText": "yes", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "27859de502b5ad0bc238", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "545190bd06d9b03052d9", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ], + [ + 602.4, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "dfd7373464d5a149547c", + "type": "text", + "x": 14.0, + "y": 126.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": "int.numerator", + "originalText": "int.numerator", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "016d0bbab348a66994d6", + "type": "text", + "x": 205.2, + "y": 126.0, + "width": 163.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "getset_descriptor", + "originalText": "getset_descriptor", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2e8468642c5798217e64", + "type": "text", + "x": 444.4, + "y": 126.0, + "width": 28.799999999999997, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "yes", + "originalText": "yes", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "c3d7ea4b0405dc09b1e3", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "65a4dd6e7e4be42759e1", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ], + [ + 602.4, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "50c2a317d3d03500fc11", + "type": "text", + "x": 14.0, + "y": 166.0, + "width": 76.8, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "str.join", + "originalText": "str.join", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "6523221ff385dbe31d85", + "type": "text", + "x": 205.2, + "y": 166.0, + "width": 163.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "method_descriptor", + "originalText": "method_descriptor", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f61a6c3ba6d0840a68f1", + "type": "text", + "x": 444.4, + "y": 166.0, + "width": 19.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "no", + "originalText": "no", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "4170b4b2cd464cf68f35", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "69a65a66dfdfe14623f9", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ], + [ + 602.4, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "4769fa6ef43758463854", + "type": "text", + "x": 14.0, + "y": 206.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": "int.__add__", + "originalText": "int.__add__", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "1c1729fac8cd058e65e0", + "type": "text", + "x": 205.2, + "y": 206.0, + "width": 172.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": "wrapper_descriptor", + "originalText": "wrapper_descriptor", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "725e77b86456db4c33a8", + "type": "text", + "x": 444.4, + "y": 206.0, + "width": 19.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "no", + "originalText": "no", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "704b829e5ada8e5bccc1", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "7c163131cdaa7faac5b3", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ], + [ + 602.4, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "ee54d6414f0d11091f63", + "type": "text", + "x": 14.0, + "y": 246.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": "dict.fromkeys", + "originalText": "dict.fromkeys", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f7958f4714da616e4767", + "type": "text", + "x": 205.2, + "y": 246.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": "classmethod_descriptor", + "originalText": "classmethod_descriptor", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "eb30939b08a512aa6dd6", + "type": "text", + "x": 444.4, + "y": 246.0, + "width": 19.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "no", + "originalText": "no", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "c2b01760a7eae363a0a1", + "type": "line", + "x": 0.0, + "y": 276.0, + "width": 602.4, + "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 + ], + [ + 602.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "d403843d33964fd840cc", + "type": "text", + "x": 0.0, + "y": 296.0, + "width": 588.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": "Same protocol, same __get__ call. Only the constructor is different.", + "originalText": "Same protocol, same __get__ call. Only the constructor is different.", + "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/o06-what-property-actually-is/diagrams/the-c-side.svg b/lessons/o06-what-property-actually-is/diagrams/the-c-side.svg new file mode 100644 index 0000000..1cafc82 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/the-c-side.svg @@ -0,0 +1 @@ +C code cannot write a class body, so it builds these insteadwhere you find itthe type you getdata descriptora __slots__ entrymember_descriptoryesint.numeratorgetset_descriptoryesstr.joinmethod_descriptornoint.__add__wrapper_descriptornodict.fromkeysclassmethod_descriptornoSame protocol, same __get__ call. Only the constructor is different. diff --git a/lessons/o06-what-property-actually-is/diagrams/the-three-methods.excalidraw b/lessons/o06-what-property-actually-is/diagrams/the-three-methods.excalidraw new file mode 100644 index 0000000..3d6c517 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/the-three-methods.excalidraw @@ -0,0 +1,403 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "e2d69dea3b7047830f3a", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 667.4399999999999, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "The whole protocol, and there is nothing else to it", + "originalText": "The whole protocol, and there is nothing else to it", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e9e598424ba77c09e239", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 824.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "500f4dac8b2fa4f3d699", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "500f4dac8b2fa4f3d699", + "type": "text", + "x": 16.0, + "y": 66.0, + "width": 792.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": "__set_name__(self, owner, name) runs once, when the class is built", + "originalText": "__set_name__(self, owner, name) runs once, when the class is built", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "e9e598424ba77c09e239", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "a6f6ea31898213f82226", + "type": "text", + "x": 840.0, + "y": 66.0, + "width": 25.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "top", + "originalText": "top", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7099a615ce4909cd0db9", + "type": "rectangle", + "x": 0.0, + "y": 102.0, + "width": 824.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "22a124f9430fd9e1d23a", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "22a124f9430fd9e1d23a", + "type": "text", + "x": 16.0, + "y": 118.0, + "width": 792.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": "__delete__(self, obj) runs on del", + "originalText": "__delete__(self, obj) runs on del", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "7099a615ce4909cd0db9", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "9b1a3cdcc9394e198329", + "type": "rectangle", + "x": 0.0, + "y": 154.0, + "width": 824.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "6756a09f69b437d79278", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "6756a09f69b437d79278", + "type": "text", + "x": 16.0, + "y": 170.0, + "width": 792.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": "__set__(self, obj, value) runs when something assigns to it", + "originalText": "__set__(self, obj, value) runs when something assigns to it", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "9b1a3cdcc9394e198329", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "4752889a92cc7da09d28", + "type": "rectangle", + "x": 0.0, + "y": 206.0, + "width": 824.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "c1817e737ae346b9e8c7", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "c1817e737ae346b9e8c7", + "type": "text", + "x": 16.0, + "y": 222.0, + "width": 792.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": "__get__(self, obj, owner) runs when something reads the attribute", + "originalText": "__get__(self, obj, owner) runs when something reads the attribute", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "4752889a92cc7da09d28", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "b6e9a82b6e24f9c93bf9", + "type": "text", + "x": 0.0, + "y": 278.0, + "width": 831.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "Define __get__ only and you have a non data descriptor. Add __set__ and the precedence flips.", + "originalText": "Define __get__ only and you have a non data descriptor. Add __set__ and the precedence flips.", + "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/o06-what-property-actually-is/diagrams/the-three-methods.svg b/lessons/o06-what-property-actually-is/diagrams/the-three-methods.svg new file mode 100644 index 0000000..fada676 --- /dev/null +++ b/lessons/o06-what-property-actually-is/diagrams/the-three-methods.svg @@ -0,0 +1 @@ +The whole protocol, and there is nothing else to it__set_name__(self, owner, name) runs once, when the class is builttop__delete__(self, obj) runs on del__set__(self, obj, value) runs when something assigns to it__get__(self, obj, owner) runs when something reads the attributeDefine __get__ only and you have a non data descriptor. Add __set__ and the precedence flips. diff --git a/lessons/o06-what-property-actually-is/o06.ipynb b/lessons/o06-what-property-actually-is/o06.ipynb new file mode 100644 index 0000000..3c4e016 --- /dev/null +++ b/lessons/o06-what-property-actually-is/o06.ipynb @@ -0,0 +1,590 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "o06-01", + "metadata": {}, + "source": [ + "# O06. What property actually is\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/o06-what-property-actually-is/o06.ipynb)\n", + "\n", + "O05 said a data descriptor wins over the instance dict, and that a plain function does not. It never said what a descriptor is.\n", + "\n", + "Here is the short answer. Put an object in a class dict, give it a `__get__` method, and reading that attribute calls the method instead of handing back the object.\n", + "\n", + "![four familiar features, one protocol, and the line counts](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o06-what-property-actually-is/diagrams/four-of-a-kind.svg)\n", + "\n", + "That is it. `property` is that. `classmethod` is that. So is `staticmethod`, and so is the `self` in every method you have ever written. Four things people learn as four separate features, and three of them are under ten lines of C.\n", + "\n", + "This lesson builds one by hand, then reads the four real ones." + ] + }, + { + "cell_type": "markdown", + "id": "o06-02", + "metadata": {}, + "source": [ + "## About the source references\n", + "\n", + "Now and then this lesson points at CPython's own source, like this: `Objects/funcobject.c:1264-1270@v3.15.0rc1#func_descr_get`.\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": "o06-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": "o06-04", + "metadata": {}, + "source": [ + "## Which Python is this\n", + "\n", + "Everything below was checked against the version this cell prints and against 3.14. The two agree on every line of output in this lesson." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-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": "o06-06", + "metadata": {}, + "source": [ + "## Three methods, and one of them changes everything\n", + "\n", + "A [any object that defines __get__, __set__ or __delete__ and is stored in a class dict](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#descriptor) is any object with `__get__`, `__set__` or `__delete__` on its type. There is no base class to inherit from and no registration step. Having the method is the whole qualification.\n", + "\n", + "![the three methods of the protocol, plus __set_name__](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o06-what-property-actually-is/diagrams/the-three-methods.svg)\n", + "\n", + "Write one and you can watch the calls go past.\n", + "\n", + "an object with __get__ and __set__ in a class dict intercepts both reads and writes of that attribute on every instance of the class" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-07", + "metadata": {}, + "outputs": [], + "source": [ + "class Logged:\n", + " \"\"\"Stores in the instance dict like normal, and prints every call on the way through.\"\"\"\n", + "\n", + " def __set_name__(self, owner, name):\n", + " self.name = name\n", + " print(f\" __set_name__ ran for {name!r} on {owner.__name__}\")\n", + "\n", + " def __get__(self, obj, owner):\n", + " if obj is None:\n", + " return self\n", + " print(f\" __get__ called with obj={obj!r} owner={owner.__name__}\")\n", + " return obj.__dict__[self.name]\n", + "\n", + " def __set__(self, obj, value):\n", + " print(f\" __set__ called with value={value!r}\")\n", + " obj.__dict__[self.name] = value\n", + "\n", + "\n", + "class Point:\n", + " x = Logged()\n", + "\n", + " def __repr__(self):\n", + " return \"a Point\"\n", + "\n", + "\n", + "p = Point()\n", + "p.x = 3\n", + "print(f\" p.x is {p.x}\")\n", + "print(f\" and the instance dict holds {p.__dict__}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-08", + "metadata": {}, + "source": [ + "Notice what the class body did not do. It never called `__set_name__`, and `Logged` never asked what attribute it was assigned to. Something ran it during the class statement, and there is a section on that at the end.\n", + "\n", + "The other thing to notice is `if obj is None`. `__get__` gets called for `Point.x` as well as for `p.x`, and in the class case there is no instance to pass, so `obj` is `None`. Returning `self` there is the convention, and it is why `Point.x` in a debugger shows you the descriptor rather than blowing up.\n", + "\n", + "Now the split O05 kept referring to. `Logged` has `__set__`, so it is a [a descriptor whose type fills in tp_descr_set, meaning it has __set__ or __delete__](https://github.com/tamnd/cpython-internals/blob/main/GLOSSARY.md#data-descriptor) and it beats the instance dict. Take `__set__` away and it becomes a non data descriptor, and the instance dict beats it instead.\n", + "\n", + "![the one difference between a data and a non data descriptor](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o06-what-property-actually-is/diagrams/data-or-not.svg)\n", + "\n", + "The test in C is one line, [Objects/descrobject.c:1028-1032@v3.15.0rc1#PyDescr_IsData](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L1028-L1032). It asks whether the descriptor's type has `tp_descr_set` filled in. Not whether the method does anything useful, not whether it is `__set__` or `__delete__`, just whether the slot is non null. That single bit decides the precedence question for every attribute in Python." + ] + }, + { + "cell_type": "markdown", + "id": "o06-09", + "metadata": {}, + "source": [ + "## Every function you have ever written is a descriptor\n", + "\n", + "Start with the one nobody thinks of as a descriptor. A `def` inside a class body makes a plain function object and puts it in the class dict. Nothing else happens at that point.\n", + "\n", + "Then you write `g.greet()` and get a bound method, with `self` already filled in. That happens because `function` has a `__get__`.\n", + "\n", + "![how a plain function turns into a bound method](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o06-what-property-actually-is/diagrams/a-function-becomes-a-method.svg)\n", + "\n", + "reading a method off an instance calls the function's __get__ and produces a new bound method object each time, while reading it off the class returns the plain function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-10", + "metadata": {}, + "outputs": [], + "source": [ + "class Greeter:\n", + " def hello(self):\n", + " return \"hi\"\n", + "\n", + " def __repr__(self):\n", + " return \"a Greeter\"\n", + "\n", + "\n", + "g = Greeter()\n", + "raw = Greeter.__dict__[\"hello\"]\n", + "print(f\" Greeter.__dict__['hello'] is a {type(raw).__name__}\")\n", + "print(f\" Greeter.hello is that same object {Greeter.hello is raw}\")\n", + "print(f\" g.hello is not {g.hello is raw}\")\n", + "print(f\" g.hello {g.hello}\")\n", + "print(f\" g.hello.__func__ is raw {g.hello.__func__ is raw}\")\n", + "print(f\" g.hello.__self__ is g {g.hello.__self__ is g}\")\n", + "print(f\" raw.__get__(g, Greeter) {raw.__get__(g, Greeter)}\")\n", + "print(f\" raw.__get__(None, Greeter) is raw {raw.__get__(None, Greeter) is raw}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-11", + "metadata": {}, + "source": [ + "The C is seven lines, [Objects/funcobject.c:1264-1270@v3.15.0rc1#func_descr_get](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/funcobject.c#L1264-L1270). If `obj` is `NULL` or `None`, hand back the function unchanged. Otherwise call `PyMethod_New`, [Objects/classobject.c:64-84@v3.15.0rc1#PyMethod_New](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/classobject.c#L64-L84), which allocates a small object holding two pointers: the function and the instance. That is the entire mechanism behind `self`.\n", + "\n", + "There is no `__set__` there, which makes a function a non data descriptor, which is why `g.hello = something_else` works and shadows the method for that one instance. A `property` would refuse.\n", + "\n", + "The other consequence is that `g.hello` is a fresh object every time. Two reads give you two different bound methods that happen to compare equal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-12", + "metadata": {}, + "outputs": [], + "source": [ + "a = g.hello\n", + "b = g.hello\n", + "print(f\" g.hello is g.hello {a is b}\")\n", + "print(f\" g.hello == g.hello {a == b}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-13", + "metadata": {}, + "source": [ + "This is why `obj.method` in a hot loop is not free, and it is also why the interpreter has a `LOAD_METHOD` style specialisation that calls the function directly and skips building the object. `PyMethod_New` does pull from a free list when it can, which takes the edge off. O12 is about those free lists.\n", + "\n", + "## classmethod and staticmethod are a few lines each\n", + "\n", + "Both are descriptors, both are plain Python level objects you can construct yourself, and both are shorter than you would guess.\n", + "\n", + "`staticmethod` is six lines, [Objects/funcobject.c:1794-1799@v3.15.0rc1#sm_descr_get](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/funcobject.c#L1794-L1799). Its `__get__` returns the wrapped callable and ignores both arguments. That is the entire feature. It exists to stop the function's own `__get__` from running.\n", + "\n", + "`classmethod` is eight, [Objects/funcobject.c:1530-1537@v3.15.0rc1#cm_descr_get](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/funcobject.c#L1530-L1537). It calls the same `PyMethod_New` a plain function does, but passes the class instead of the instance. So a classmethod really is a bound method, bound to a class object.\n", + "\n", + "classmethod produces a bound method whose __self__ is the class, and the class it binds to is the one the lookup started from rather than the one that defined the method" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-14", + "metadata": {}, + "outputs": [], + "source": [ + "class Tools:\n", + " @classmethod\n", + " def cm(cls):\n", + " return cls\n", + "\n", + " @staticmethod\n", + " def sm():\n", + " return \"plain\"\n", + "\n", + "\n", + "sm_raw = Tools.__dict__[\"sm\"]\n", + "print(f\" Tools.cm {Tools.cm}\")\n", + "print(f\" Tools.cm.__self__ is Tools {Tools.cm.__self__ is Tools}\")\n", + "print(f\" Tools().cm.__self__ is Tools {Tools().cm.__self__ is Tools}\")\n", + "print(f\" Tools.sm() {Tools.sm()}\")\n", + "print(f\" staticmethod handed the function straight back {Tools.sm is sm_raw.__wrapped__}\")\n", + "\n", + "\n", + "class Sub(Tools):\n", + " pass\n", + "\n", + "\n", + "print(f\" Sub.cm() is Sub {Sub.cm() is Sub}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-15", + "metadata": {}, + "source": [ + "That last line is the useful part. `cm_descr_get` receives whichever class the lookup started from, so `Sub.cm()` gets `Sub` even though the method was defined on `Tools`. Alternative constructors work because of that one argument.\n", + "\n", + "`Tools().cm` binds to the class too, not the instance. The C takes `type` when it has one and falls back to `Py_TYPE(obj)` when it does not, so calling a classmethod through an instance still gives you the class.\n", + "\n", + "## property is the one with a setter\n", + "\n", + "`property` is the longest of the four and still not long. Its `__get__` is [Objects/descrobject.c:1685-1718@v3.15.0rc1#property_descr_get](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L1685-L1718), and most of those lines are the error message for a property with no getter. The working part is two lines: return `self` when there is no instance, otherwise call the getter with the instance as its only argument.\n", + "\n", + "What makes it different from the other three is `property_descr_set`, [Objects/descrobject.c:1720-1755@v3.15.0rc1#property_descr_set](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L1720-L1755). Having that filled in is what makes `property` a data descriptor, and it is the only reason a property cannot be shadowed by an instance attribute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-16", + "metadata": {}, + "outputs": [], + "source": [ + "class Config:\n", + " def __init__(self):\n", + " self._value = 1\n", + "\n", + " @property\n", + " def value(self):\n", + " return self._value\n", + "\n", + " @value.setter\n", + " def value(self, new):\n", + " self._value = new\n", + "\n", + " def __repr__(self):\n", + " return \"a Config\"\n", + "\n", + "\n", + "c = Config()\n", + "prop = Config.__dict__[\"value\"]\n", + "print(f\" Config.__dict__['value'] is a {type(prop).__name__}\")\n", + "print(f\" and it has __set__ {hasattr(prop, '__set__')}\")\n", + "c.value = 5\n", + "print(f\" c.value {c.value}\")\n", + "print(f\" c.__dict__ {c.__dict__}\")\n", + "\n", + "c.__dict__[\"value\"] = \"a direct write into the instance dict\"\n", + "print(f\" after writing straight into the dict, c.value is still {c.value}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-17", + "metadata": {}, + "source": [ + "The last two lines are worth sitting with. The instance dict genuinely has a `value` key in it now. Nothing raised, nothing cleaned it up, and the attribute still reads `5`. Attribute lookup checked the type first, found a data descriptor, and never looked at the dict at all. The key is just sitting there being ignored.\n", + "\n", + "That is the same rule from O05, seen from the other side.\n", + "\n", + "The error path is worth a look too, since the message tells you exactly which of the three functions was missing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-18", + "metadata": {}, + "outputs": [], + "source": [ + "class ReadOnly:\n", + " @property\n", + " def frozen(self):\n", + " return 1\n", + "\n", + "\n", + "target = ReadOnly()\n", + "attempts = [\n", + " (\"assign\", lambda: setattr(target, \"frozen\", 2)),\n", + " (\"delete\", lambda: delattr(target, \"frozen\")),\n", + "]\n", + "for label, action in attempts:\n", + " try:\n", + " action()\n", + " except AttributeError as error:\n", + " print(f\" {label} {error}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-19", + "metadata": {}, + "source": [ + "Both go through `property_descr_set`. It picks `prop_del` when the value is `NULL` and `prop_set` otherwise, finds neither is set, and formats a message naming the property and the class. The property learned its own name from `__set_name__`, which is the same mechanism `Logged` used at the top.\n", + "\n", + "## The descriptors C code builds\n", + "\n", + "C code cannot write a class body. When an extension type declares its attributes it fills in tables of C structs, and CPython turns those into descriptor objects when the type is created. There are five of them and you have been using them all along.\n", + "\n", + "![the five descriptor types C code produces, and which are data descriptors](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o06-what-property-actually-is/diagrams/the-c-side.svg)\n", + "\n", + "attributes defined from C appear as one of five descriptor types, and whether each is a data descriptor is decided by whether its type fills in tp_descr_set" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-20", + "metadata": {}, + "outputs": [], + "source": [ + "class Slotted:\n", + " __slots__ = (\"count\",)\n", + "\n", + "\n", + "rows = [\n", + " (\"a __slots__ entry\", Slotted.__dict__[\"count\"]),\n", + " (\"int.numerator\", int.__dict__[\"numerator\"]),\n", + " (\"str.join\", str.__dict__[\"join\"]),\n", + " (\"int.__add__\", int.__dict__[\"__add__\"]),\n", + " (\"dict.fromkeys\", dict.__dict__[\"fromkeys\"]),\n", + "]\n", + "for label, descr in rows:\n", + " kind = type(descr).__name__\n", + " data = \"data\" if hasattr(descr, \"__set__\") else \"non data\"\n", + " print(f\" {label:18} {kind:23} {data}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-21", + "metadata": {}, + "source": [ + "Five types, one protocol. `member_descriptor` reads a field at a fixed byte offset in the object, [Objects/descrobject.c:163-181@v3.15.0rc1#member_get](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L163-L181), which is what every `__slots__` entry is and what O03 was really describing. `getset_descriptor` calls a C getter function, [Objects/descrobject.c:183-201@v3.15.0rc1#getset_get](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L183-L201). `method_descriptor` binds a C function to the instance, [Objects/descrobject.c:137-160@v3.15.0rc1#method_get](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L137-L160), the C equivalent of `func_descr_get`. `wrapper_descriptor` wraps a slot function so you can call it from Python, [Objects/descrobject.c:203-214@v3.15.0rc1#wrapperdescr_get](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L203-L214). `classmethod_descriptor` is the C version of `classmethod`, [Objects/descrobject.c:94-130@v3.15.0rc1#classmethod_get](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L94-L130).\n", + "\n", + "The two data descriptors in that list, `member_descriptor` and `getset_descriptor`, fill in `tp_descr_set`, [Objects/descrobject.c:794-828@v3.15.0rc1#PyMemberDescr_Type](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L794-L828). The other three leave it at zero and are non data descriptors, which is exactly why you can assign over `str.join` on a subclass and cannot assign over a slot.\n", + "\n", + "These are the descriptors that carry the type check people run into. A Python descriptor gets whatever instance it is handed. A C one checks first, [Objects/descrobject.c:216-240@v3.15.0rc1#descr_setcheck](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/descrobject.c#L216-L240), because writing an `int` field at a fixed offset in an object that is not an `int` would corrupt memory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-22", + "metadata": {}, + "outputs": [], + "source": [ + "try:\n", + " Slotted.__dict__[\"count\"].__set__(\"not a Slotted at all\", 1)\n", + "except TypeError as error:\n", + " print(f\" {error}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-23", + "metadata": {}, + "source": [ + "## It only counts on the type\n", + "\n", + "One rule that catches people. The protocol is only consulted for objects found on the type. Put a descriptor in an instance dict and it is an ordinary value.\n", + "\n", + "a descriptor placed in an instance dict is returned as itself, because the lookup that would call __get__ only runs for objects found on the type" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-24", + "metadata": {}, + "outputs": [], + "source": [ + "class Sneaky:\n", + " def __get__(self, obj, owner):\n", + " return \"the descriptor fired\"\n", + "\n", + "\n", + "class Plain:\n", + " pass\n", + "\n", + "\n", + "holder = Plain()\n", + "holder.from_the_instance = Sneaky()\n", + "Plain.from_the_class = Sneaky()\n", + "\n", + "print(f\" holder.from_the_instance {type(holder.from_the_instance).__name__}\")\n", + "print(f\" holder.from_the_class {holder.from_the_class}\")\n", + "print(f\" Plain.from_the_class {Plain.from_the_class}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-25", + "metadata": {}, + "source": [ + "Look back at O05's four steps and the reason is obvious. The instance dict branch reads the value and returns it, [Objects/object.c:1887-1926@v3.15.0rc1#_PyObject_GenericGetAttrWithDict](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/object.c#L1887-L1926). Only the MRO branch checks for `__get__`.\n", + "\n", + "That is not an oversight. If the instance dict were checked too, storing a function on an instance would silently turn it into a method, and every dict value would have to be tested for a descriptor slot before being handed back.\n", + "\n", + "## How a descriptor learns its own name\n", + "\n", + "Back to the loose end. `Logged` printed the attribute it was assigned to without being told, because `type` calls `__set_name__` on every value in the class dict on the way to building the class.\n", + "\n", + "![when __set_name__ runs during class creation](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/o06-what-property-actually-is/diagrams/set-name-timing.svg)\n", + "\n", + "The function is `type_new_set_names`, [Objects/typeobject.c:12274-12308@v3.15.0rc1#type_new_set_names](https://github.com/python/cpython/blob/v3.15.0rc1/Objects/typeobject.c#L12274-L12308). It copies the class dict, walks the copy, looks up `__set_name__` on each value, and calls it with the class and the key. The copy matters. It is why a descriptor can add more attributes to the class from inside `__set_name__` without the loop tripping over them.\n", + "\n", + "__set_name__ runs once for every entry in the class dict during class creation, and never runs again for later assignments to the class" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-26", + "metadata": {}, + "outputs": [], + "source": [ + "print(\" about to run the class statement\")\n", + "\n", + "\n", + "class Watched:\n", + " first = Logged()\n", + " second = Logged()\n", + "\n", + "\n", + "print(\" class statement finished, and both had already run\")\n", + "\n", + "Watched.third = Logged()\n", + "print(\" assigning a descriptor to the class afterwards printed nothing\")\n", + "print(f\" so Watched.__dict__['third'] has no name: {hasattr(Watched.__dict__['third'], 'name')}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-27", + "metadata": {}, + "source": [ + "The last line is the trap. Adding a descriptor to a class after the class statement gets you a descriptor that never learned its name, and it will fail the first time something reads it. Decorators that rewrite classes have to call `__set_name__` themselves, and `dataclasses` is one of the places in the standard library that does.\n", + "\n", + "Errors get a note rather than being swallowed. If `__set_name__` raises, the class statement fails and the message tells you which descriptor and which attribute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "o06-28", + "metadata": {}, + "outputs": [], + "source": [ + "class Fussy:\n", + " def __set_name__(self, owner, name):\n", + " raise ValueError(\"this name will not do\")\n", + "\n", + "\n", + "try:\n", + "\n", + " class Doomed:\n", + " field = Fussy()\n", + "\n", + "except ValueError as error:\n", + " print(f\" the error {error}\")\n", + " for note in error.__notes__:\n", + " print(f\" the note {note}\")" + ] + }, + { + "cell_type": "markdown", + "id": "o06-29", + "metadata": {}, + "source": [ + "## Try it yourself\n", + "\n", + "Three things to poke at.\n", + "\n", + "Write a caching descriptor. `__get__` computes a value, writes it into `obj.__dict__` under its own name, and returns it. Leave `__set__` off. The second read never reaches the descriptor at all, because a non data descriptor loses to the instance dict, and you get caching for free with no cache lookup. Then add an empty `__set__` and watch the caching stop working.\n", + "\n", + "Take `property` apart and put it back together in Python. A class holding three functions, a `__get__` that calls the first, a `__set__` that calls the second or third, and a `__set_name__` that remembers the name for the error messages. Under twenty lines, and it will behave the same as the real one for everything in this notebook.\n", + "\n", + "Find the descriptors on a type you use. `[n for n, v in vars(dict).items() if hasattr(type(v), \"__get__\")]` will list them, and the interesting part is how few things on a type are not descriptors.\n", + "\n", + "## What just happened\n", + "\n", + "A descriptor is any object in a class dict whose type has `__get__`, `__set__` or `__delete__`. There is nothing to inherit from and nothing to register. Reading such an attribute calls `__get__` instead of returning the object.\n", + "\n", + "Whether it also has `__set__` or `__delete__` decides everything about precedence. The check is one line of C that asks whether `tp_descr_set` is non null. With it, the descriptor beats the instance dict and cannot be shadowed. Without it, the instance dict wins.\n", + "\n", + "Functions are descriptors, which is where `self` comes from. `func_descr_get` is seven lines and hands back either the function or a new two pointer object holding the function and the instance. `staticmethod` is six lines that hand the callable back unchanged. `classmethod` is eight that bind to the class the lookup started from, which is what makes alternative constructors inherit properly. `property` is the long one at about thirty lines, and the extra length is error messages.\n", + "\n", + "C code builds five descriptor types because it cannot write a class body. Two of them, the ones behind `__slots__` and behind read only attributes like `int.numerator`, are data descriptors. Those two also type check their instance before touching memory.\n", + "\n", + "The protocol only applies to objects found on the type. In an instance dict a descriptor is just a value.\n", + "\n", + "`__set_name__` runs once during class creation, over a copy of the class dict, and never runs again. A descriptor assigned to a class afterwards never learns its name.\n", + "\n", + "## What is next\n", + "\n", + "O07 is the dict. Every lesson so far has said \"look in the instance dict\" or \"look in the class dict\" as if that were a single simple step, and it is the most tuned data structure in the interpreter. It is a hash table with open addressing, it keeps insertion order without spending anything on it, and it has two completely different memory layouts depending on how it was made. O08 is about the second of those layouts, which is the one that makes ordinary objects small." + ] + } + ], + "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 d8208f2..7594743 100644 --- a/probes/pyodide/lessons.json +++ b/probes/pyodide/lessons.json @@ -539,7 +539,7 @@ { "name": "f10-11", "status": "ok", - "printed": " -- MAKE_CELL 0 (a)\n MAKE_CELL 6 (n)\n\n 1 RESUME 0\n\n 2 LOAD_SMALL_INT 0\n STORE_DEREF 6 (n)\n\n 4 LOAD_FAST_BORROW 0 (a)\n LOAD_FAST_BORROW 6 (n)\n BUILD_TUPLE 2\n LOAD_CONST 1 ()\n MAKE_FUNCTION\n SET_FUNCTION_ATTRIBUTE 8 (closure)\n STORE_FAST 5 (inner)\n\n 7 LOAD_FAST_BORROW 5 (inner)\n RETURN_VALUE\n\nDisassembly of :\n -- COPY_FREE_VARS 2\n\n 4 RESUME 0\n\n 5 LOAD_DEREF 0 (a)\n LOAD_DEREF 1 (n)\n BINARY_OP 0 (+)\n RETURN_VALUE\n" + "printed": " -- MAKE_CELL 0 (a)\n MAKE_CELL 6 (n)\n\n 1 RESUME 0\n\n 2 LOAD_SMALL_INT 0\n STORE_DEREF 6 (n)\n\n 4 LOAD_FAST_BORROW 0 (a)\n LOAD_FAST_BORROW 6 (n)\n BUILD_TUPLE 2\n LOAD_CONST 1 ()\n MAKE_FUNCTION\n SET_FUNCTION_ATTRIBUTE 8 (closure)\n STORE_FAST 5 (inner)\n\n 7 LOAD_FAST_BORROW 5 (inner)\n RETURN_VALUE\n\nDisassembly of :\n -- COPY_FREE_VARS 2\n\n 4 RESUME 0\n\n 5 LOAD_DEREF 0 (a)\n LOAD_DEREF 1 (n)\n BINARY_OP 0 (+)\n RETURN_VALUE\n" }, { "name": "f10-13", @@ -631,12 +631,12 @@ { "name": "f12-07", "status": "ok", - "printed": " greet.py is 54 bytes of text\n greet.cpython-314.pyc is 251 bytes\n\n header 2b 0e 0d 0a 00 00 00 00 42 ad 95 6a 36 00 00 00\n the rest 235 bytes, and marshal.loads gives back a\n code object\n" + "printed": " greet.py is 54 bytes of text\n greet.cpython-314.pyc is 251 bytes\n\n header 2b 0e 0d 0a 00 00 00 00 76 b0 95 6a 36 00 00 00\n the rest 235 bytes, and marshal.loads gives back a\n code object\n" }, { "name": "f12-10", "status": "ok", - "printed": " magic 168627755\n flags 0\n mtime 1788194114 source 1788194114\n size 54 source 54\n" + "printed": " magic 168627755\n flags 0\n mtime 1788194934 source 1788194934\n size 54 source 54\n" }, { "name": "f12-13", @@ -670,7 +670,7 @@ { "name": "f12-29", "status": "ok", - "printed": " one bit flipped in the magic: bad magic number in 'broken': b'*\\x0e\\r\\n'\n\n put a wrong mtime in greet.cpython-314.pyc: 1788194237\n imported it with no complaint: 'hi world'\n and the header now says 1788194237\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: 1788194953\n imported it with no complaint: 'hi world'\n and the header now says 1788194953\n the file was quietly rewritten: False\n" } ] }, @@ -689,7 +689,7 @@ { "name": "o01-07", "status": "ok", - "printed": " a pointer here is 4 bytes, so the header is 8\n it lives at 25574120\n\n first word, read as a count 1\n sys.getrefcount, less its own argument 1\n the same number: True\n\n second word 24505536\n id(Thing) is 24505536\n so the second word is the type pointer: True\n\n after a second name, the count reads 2\n after deleting that name, it reads 1\n" + "printed": " a pointer here is 4 bytes, so the header is 8\n it lives at 25455504\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 23942736\n id(Thing) is 23942736\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", @@ -929,6 +929,75 @@ } ] }, + { + "slug": "o06-what-property-actually-is", + "cells": [ + { + "name": "o06-03", + "status": "ok" + }, + { + "name": "o06-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": "o06-07", + "status": "ok", + "printed": " __set_name__ ran for 'x' on Point\n __set__ called with value=3\n __get__ called with obj=a Point owner=Point\n p.x is 3\n and the instance dict holds {'x': 3}\n" + }, + { + "name": "o06-10", + "status": "ok", + "printed": " Greeter.__dict__['hello'] is a function\n Greeter.hello is that same object True\n g.hello is not False\n g.hello \n g.hello.__func__ is raw True\n g.hello.__self__ is g True\n raw.__get__(g, Greeter) \n raw.__get__(None, Greeter) is raw True\n" + }, + { + "name": "o06-12", + "status": "ok", + "printed": " g.hello is g.hello False\n g.hello == g.hello True\n" + }, + { + "name": "o06-14", + "status": "ok", + "printed": " Tools.cm >\n Tools.cm.__self__ is Tools True\n Tools().cm.__self__ is Tools True\n Tools.sm() plain\n staticmethod handed the function straight back True\n Sub.cm() is Sub True\n" + }, + { + "name": "o06-16", + "status": "ok", + "printed": " Config.__dict__['value'] is a property\n and it has __set__ True\n c.value 5\n c.__dict__ {'_value': 5}\n after writing straight into the dict, c.value is still 5\n" + }, + { + "name": "o06-18", + "status": "ok", + "printed": " assign property 'frozen' of 'ReadOnly' object has no setter\n delete property 'frozen' of 'ReadOnly' object has no deleter\n" + }, + { + "name": "o06-20", + "status": "ok", + "printed": " a __slots__ entry member_descriptor data\n int.numerator getset_descriptor data\n str.join method_descriptor non data\n int.__add__ wrapper_descriptor non data\n dict.fromkeys classmethod_descriptor non data\n" + }, + { + "name": "o06-22", + "status": "ok", + "printed": " descriptor 'count' for 'Slotted' objects doesn't apply to a 'str' object\n" + }, + { + "name": "o06-24", + "status": "ok", + "printed": " holder.from_the_instance Sneaky\n holder.from_the_class the descriptor fired\n Plain.from_the_class the descriptor fired\n" + }, + { + "name": "o06-26", + "status": "ok", + "printed": " about to run the class statement\n __set_name__ ran for 'first' on Watched\n __set_name__ ran for 'second' on Watched\n class statement finished, and both had already run\n assigning a descriptor to the class afterwards printed nothing\n so Watched.__dict__['third'] has no name: False\n" + }, + { + "name": "o06-28", + "status": "ok", + "printed": " the error this name will not do\n the note Error calling __set_name__ on 'Fussy' instance 'field' in 'Doomed'\n" + } + ] + }, { "slug": "t01-one-line-seven-stages", "cells": [ @@ -1311,7 +1380,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", @@ -1588,7 +1657,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", @@ -1622,12 +1691,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 0x1765610, 3 reference(s), 26 bytes, not tracked\nlist at 0x1772e00, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x158e610, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x1415bf8, 4 reference(s), 84 bytes, tracked by the cycle collector\n" + "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 0x14a7718, 3 reference(s), 26 bytes, not tracked\nlist at 0x16e7408, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x162e4d0, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x1557cf8, 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) 0x14869e8\nid(b) 0x17c63d8\nid(c) 0x14869e8\n" + "printed": "a == b True same contents\na is b False different objects\na is c True same object\n\nid(a) 0x16100a0\nid(b) 0x16e7468\nid(c) 0x16100a0\n" }, { "name": "t08-17", @@ -1667,7 +1736,7 @@ { "name": "t08-35", "status": "ok", - "printed": "dict at 0x11c1d40\ndict at 0x125de30\nlist at 0x16fe6f8\n" + "printed": "dict at 0x11ef2a0\ndict at 0x1240240\nlist at 0x15a4b80\n" }, { "name": "t08-37", @@ -1726,7 +1795,7 @@ { "name": "t09-21", "status": "ok", - "printed": "Node at 0x12cb060 -> Node at 0x1296770 -> Node at 0x16c2a08 -> Node at 0x12cb060\n" + "printed": "Node at 0x16128d0 -> Node at 0x1592680 -> Node at 0x170d880 -> Node at 0x16128d0\n" }, { "name": "t09-23", @@ -1756,7 +1825,7 @@ { "name": "t09-37", "status": "ok", - "printed": "first object was at 0x1699c08\nsecond object is at 0x1699c08\nsame address reused -> True\n" + "printed": "first object was at 0x1592330\nsecond object is at 0x1592330\nsame address reused -> True\n" } ] }, diff --git a/probes/pyodide/lessons.md b/probes/pyodide/lessons.md index 9895221..89b193a 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. -33 lesson(s) on Pyodide 3.14.2: 33 ran end to end, 349 cell(s) in total. +34 lesson(s) on Pyodide 3.14.2: 34 ran end to end, 362 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. @@ -29,6 +29,7 @@ The checks in `report.md` next to this ask whether a surface exists. This runs t | o03-dunders-and-slots | 9 | runs end to end | | o04-the-order-things-are-found-in | 11 | runs end to end | | o05-what-a-dot-does | 10 | runs end to end | +| o06-what-property-actually-is | 13 | 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 b317177..056764d 100644 --- a/pyxray/src/pyxray/glossary.py +++ b/pyxray/src/pyxray/glossary.py @@ -763,6 +763,23 @@ def anchor(name: str) -> str: see=("slot", "type object"), met="O03", ), + Term( + name="descriptor", + short="An object in a class dict whose type defines __get__, __set__ or __delete__.", + long="Reading an attribute that resolves to a descriptor calls `__get__` rather than handing the object back. There is nothing to inherit from and nothing to register, so having the method is the whole qualification. Functions are descriptors, which is where `self` comes from: `func_descr_get` returns the function on a class and a bound method on an instance. `property`, `classmethod`, `staticmethod`, every `__slots__` entry and most attributes defined from C are descriptors too. The protocol only applies to objects found on the type, so a descriptor sitting in an instance dict is an ordinary value.", + cite="Objects/funcobject.c:1264-1270@v3.15.0rc1#func_descr_get", + also=("descriptor protocol",), + see=("data descriptor", "slot"), + met="O06", + ), + Term( + name="bound method", + short="A small object holding a function and the instance it was read from.", + long="`PyMethod_New` allocates one with two pointers, `im_func` and `im_self`, and calling it inserts the instance as the first argument. That is all `self` is. A fresh one is built on every attribute read, so `obj.method is obj.method` is false, though the two compare equal. The allocation comes off a free list when one is available, and the interpreter specialises the common call shape so that reading and immediately calling a method skips building the object at all.", + cite="Objects/classobject.c:64-84@v3.15.0rc1#PyMethod_New", + see=("descriptor", "type object"), + met="O06", + ), Term( name="data descriptor", short="An object on a type that has __set__ or __delete__ as well as __get__.",