- Open the JupyterLite Lab (runs fully locally in the browser via a WebAssembly Python build based on Pyodide)
- File > Open from URL > paste this link
Last active
July 27, 2023 16:16
-
-
Save depau/8920287057a1ff5723d9bffe25f6662a to your computer and use it in GitHub Desktop.
Python initialization adventures
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
}, | |
"tags": [] | |
}, | |
"source": [ | |
"# Python initialization adventures\n", | |
"\n", | |
"Yo I heard you like to create classes. Let me blow your mind a little bit." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
}, | |
"tags": [] | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"class: <class '__main__.MyClass'>\n", | |
"Greetings from the constructor\n", | |
"instance: <__main__.MyClass object at 0x7fab98355890>\n", | |
"I'm a method! cucumber\n" | |
] | |
} | |
], | |
"source": [ | |
"class MyClass:\n", | |
" class_attribute = \"lol\"\n", | |
" \n", | |
" def __init__(self, value):\n", | |
" print(\"Greetings from the constructor\")\n", | |
" self.instance_attribute = value\n", | |
" \n", | |
" def method(self):\n", | |
" print(f\"I'm a method! {self.instance_attribute}\")\n", | |
"\n", | |
"print(\"class:\", MyClass)\n", | |
"\n", | |
"i = MyClass(\"cucumber\")\n", | |
"print(\"instance:\", i)\n", | |
"\n", | |
"i.method()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"---\n", | |
"But what if we want things to be a little cursed?" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": { | |
"scrolled": true | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"I'm a method! cucumber\n" | |
] | |
} | |
], | |
"source": [ | |
"MyClass.method(i)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"I'm a method! celery\n" | |
] | |
} | |
], | |
"source": [ | |
"class EmptyClass:\n", | |
" pass\n", | |
"\n", | |
"obj = EmptyClass()\n", | |
"obj.instance_attribute = \"celery\"\n", | |
"\n", | |
"MyClass.method(obj)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"Well, that's fair enough I guess?\n", | |
"\n", | |
"It's a bit weird, though, isn't it?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"## Class construction\n", | |
"\n", | |
"Let's bring up some self-hatred and attempt to construct a class by hand. The keyword `class` is not allowed!\n", | |
"\n", | |
"What do we need to do? What is a class?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"### Everything is an object\n", | |
"\n", | |
"Including a class! Let's check" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"MyClass extends object? True\n", | |
"instance extends object? True\n", | |
"method extends object? True\n" | |
] | |
} | |
], | |
"source": [ | |
"print(\"MyClass extends object? \", isinstance(MyClass, object))\n", | |
"print(\"instance extends object? \", isinstance(i, object))\n", | |
"print(\"method extends object? \", isinstance(i.method, object))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"So when I construct a class I'm effectively constructing an object, aren't I?\n", | |
"\n", | |
"What's the type of a class object?" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"type" | |
] | |
}, | |
"execution_count": 9, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"type(MyClass)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"Mmh, that's interesting. Let's check the manual." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"type?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"```\n", | |
"Init signature: type(self, /, *args, **kwargs)\n", | |
"Docstring: \n", | |
"type(object) -> the object's type\n", | |
"type(name, bases, dict, **kwds) -> a new type\n", | |
"Type: type\n", | |
"Subclasses: ABCMeta, EnumType, _AnyMeta, NamedTupleMeta, _TypedDictMeta, _DeprecatedType, PyCStructType, UnionType, PyCPointerType, PyCArrayType, ...\n", | |
"```" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"What if we..." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"__main__.TypeName" | |
] | |
}, | |
"execution_count": 16, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"t = type(\"TypeName\", (), {})\n", | |
"t" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"<__main__.TypeName at 0x7fab944aae50>" | |
] | |
}, | |
"execution_count": 17, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"t()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"Very interesting. Let's try one more thing" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 20, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"def init(self, value):\n", | |
" print(\"Greetings from the constructor\")\n", | |
" self.instance_attribute = value\n", | |
"\n", | |
"def method(self):\n", | |
" print(f\"I'm a method! {self.instance_attribute}\")\n", | |
"\n", | |
"OtherClass = type(\"OtherClass\", (), {\n", | |
" \"__init__\": init,\n", | |
" \"method\": method\n", | |
"})" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 21, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"class: <class '__main__.OtherClass'>\n", | |
"Greetings from the constructor\n", | |
"instance: <__main__.OtherClass object at 0x7fab94532f50>\n", | |
"I'm a method! pumpkin\n" | |
] | |
} | |
], | |
"source": [ | |
"print(\"class:\", OtherClass)\n", | |
"\n", | |
"i1 = OtherClass(\"pumpkin\")\n", | |
"print(\"instance:\", i1)\n", | |
"\n", | |
"i1.method()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"How did that work?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"### Object construction\n", | |
"\n", | |
"Let's refresh the steps for object construction." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"## \\_\\_new\\_\\_() and \\_\\_init\\_\\_()\n", | |
"\n", | |
"You thought `__init__()` was the constructor in Python. It's not!\n", | |
"\n", | |
"`__new__()` constructs objects, `__init__()` initializes them." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"`__new__()` is a **class method** which takes the same arguments as `__init__()`, while `__init__()` is an **instance method**.\n", | |
"\n", | |
"Let's play with them." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 22, | |
"metadata": { | |
"scrolled": true, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Constructed object <__main__.Class object at 0x7fab945335d0> of <class '__main__.Class'> with value 42\n", | |
"Initializing object <__main__.Class object at 0x7fab945335d0> with value 42\n" | |
] | |
} | |
], | |
"source": [ | |
"class Class:\n", | |
" def __new__(cls, value):\n", | |
" i = super().__new__(cls)\n", | |
" print(f\"Constructed object {i} of {cls} with value {value}\")\n", | |
" return i\n", | |
" \n", | |
" def __init__(self, value):\n", | |
" print(f\"Initializing object {self} with value {value}\")\n", | |
" self.value = value\n", | |
"\n", | |
"o = Class(42)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 24, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"ename": "TypeError", | |
"evalue": "WrongClass.__new__() takes 1 positional argument but 2 were given", | |
"output_type": "error", | |
"traceback": [ | |
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", | |
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", | |
"Cell \u001b[0;32mIn[24], line 7\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m, value):\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mvalue \u001b[38;5;241m=\u001b[39m value\n\u001b[0;32m----> 7\u001b[0m \u001b[43mWrongClass\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\n", | |
"\u001b[0;31mTypeError\u001b[0m: WrongClass.__new__() takes 1 positional argument but 2 were given" | |
] | |
} | |
], | |
"source": [ | |
"class WrongClass:\n", | |
" def __new__(cls):\n", | |
" return super().__new__(cls)\n", | |
" def __init__(self, value):\n", | |
" self.value = value\n", | |
"\n", | |
"WrongClass(1)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"## Class construction\n", | |
"\n", | |
"We saw how to construct an object, and we said that a class is an object.\n", | |
"\n", | |
"But earlier we saw that in order to construct a class we had to call the `type()` function with 3 arguments.\n", | |
"\n", | |
"Is it a function? Let's see" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 25, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Type of function: <class 'function'>\n", | |
"Type of `type`: <class 'type'>\n" | |
] | |
} | |
], | |
"source": [ | |
"def foo():\n", | |
" print(\"Hello world!\")\n", | |
"\n", | |
"print(\"Type of function: \", type(foo))\n", | |
"print(\"Type of `type`: \", type(type))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"So the type of `type` is `type`. This Python is weird isn't it?\n", | |
"\n", | |
"So is it a function or what is it?\n", | |
"\n", | |
"### What is a function after all?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"#### Doh, it's an object!!\n", | |
"\n", | |
"It's an object that implements `__call__()`!" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 27, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"<function foo at 0x7fab94508040>\n", | |
"Hello world!\n", | |
"Hello world!\n", | |
"Hello world!\n" | |
] | |
} | |
], | |
"source": [ | |
"def foo():\n", | |
" print(\"Hello world!\")\n", | |
"\n", | |
"print(foo)\n", | |
"\n", | |
"foo()\n", | |
"foo.__call__()\n", | |
"foo.__call__.__call__.__call__.__call__.__call__.__call__()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 28, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"<__main__.LookMomImAFunction object at 0x7fab7df61f50>\n", | |
"Bananas\n", | |
"Bananas\n", | |
"Bananas\n" | |
] | |
} | |
], | |
"source": [ | |
"class LookMomImAFunction:\n", | |
" def __call__(self):\n", | |
" print(\"Bananas\")\n", | |
"\n", | |
"fun_class = LookMomImAFunction()\n", | |
"print(fun_class)\n", | |
"\n", | |
"fun_class()\n", | |
"fun_class.__call__()\n", | |
"fun_class.__call__.__call__.__call__.__call__.__call__()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"Let's try something:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 30, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"<class '__main__.TypeName'>\n", | |
"<__main__.TypeName object at 0x7fab7df639d0>\n" | |
] | |
} | |
], | |
"source": [ | |
"TypeName = type.__call__(type, \"TypeName\", (), {})\n", | |
"print(TypeName)\n", | |
"print(TypeName())" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"(I actually had to add an extra argument but don't worry about it for now)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"### So what is `type`?\n", | |
"\n", | |
"Is it an object, a function, a class, a bird, a plane?\n", | |
"\n", | |
"We saw that..." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 31, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"True" | |
] | |
}, | |
"execution_count": 31, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"type(type) == type" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"So `type` is the type of itself.\n", | |
"\n", | |
"In other words `type` is an instance of the class `type`, or an instance of itself." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"You could almost say that `type` is a *metatype* or *metaclass*.\n", | |
"\n", | |
"This name sounds familiar, what a coincidence!" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"### Modifying the construction of a class\n", | |
"\n", | |
"Why would we want to do that?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"## Metaclasses\n", | |
"\n", | |
"As we saw, metaclasses are the type of the type, the class of the class.\n", | |
"\n", | |
"They are useful to tweak the creation of the class itself and, for instance, autogenerate methods." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 39, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"class TypeChild(type):\n", | |
" def __new__(cls, name, bases, attributes):\n", | |
" print(f\"Constructing class {name}, child of {bases}, containing {attributes}\")\n", | |
" i = super().__new__(cls, name, bases, attributes)\n", | |
" print(i)\n", | |
" return i\n", | |
" \n", | |
" def __init__(self, name, bases, attributes):\n", | |
" print(f\"Initializing class {name}\")\n", | |
" super().__init__(name, bases, attributes)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 41, | |
"metadata": { | |
"scrolled": true, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Constructing class ClassFoo, child of (), containing {'foo': 'bar'}\n", | |
"<class '__main__.ClassFoo'>\n", | |
"Initializing class ClassFoo\n", | |
"\n", | |
"new class: <class '__main__.ClassFoo'>\n", | |
"ClassFoo.foo: bar\n" | |
] | |
} | |
], | |
"source": [ | |
"ClassFoo = TypeChild(\"ClassFoo\", (), {\"foo\": \"bar\"})\n", | |
"print()\n", | |
"print(\"new class: \", ClassFoo)\n", | |
"print(\"ClassFoo.foo: \", ClassFoo.foo)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 44, | |
"metadata": { | |
"scrolled": true, | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"type of class: <class '__main__.TypeChild'>\n" | |
] | |
} | |
], | |
"source": [ | |
"print(\"type of class: \", type(ClassFoo))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"So the type of this new class it's no longer `type`, it's `TypeChild`!\n", | |
"\n", | |
"What if we had a better syntax for this behavior..." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 45, | |
"metadata": { | |
"scrolled": true, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Constructing class ClassBar, child of (), containing {'__module__': '__main__', '__qualname__': 'ClassBar', 'foo': 'bar'}\n", | |
"<class '__main__.ClassBar'>\n", | |
"Initializing class ClassBar\n", | |
"\n", | |
"new class: <class '__main__.ClassFoo'>\n", | |
"ClassFoo.foo: bar\n", | |
"type of class: <class '__main__.TypeChild'>\n" | |
] | |
} | |
], | |
"source": [ | |
"class ClassBar(metaclass=TypeChild):\n", | |
" foo = \"bar\"\n", | |
"\n", | |
"print()\n", | |
"print(\"new class: \", ClassFoo)\n", | |
"print(\"ClassFoo.foo: \", ClassFoo.foo)\n", | |
"print(\"type of class: \", type(ClassFoo))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"## Invoking costructors\n", | |
"\n", | |
"Now that we saw all of this, another question. Is there anything special about the class construction syntax `ClassName()`?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"No." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 49, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [], | |
"source": [ | |
"class AnotherMetaclass(TypeChild):\n", | |
" def __call__(self, *args, **kwargs):\n", | |
" print(\"About to invoke class constructor with args\", args, kwargs)\n", | |
" return super().__call__(*args, **kwargs)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 53, | |
"metadata": { | |
"scrolled": true, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Constructing class Foo, child of (), containing {'__module__': '__main__', '__qualname__': 'Foo', 'attr': 'asd', '__new__': <function Foo.__new__ at 0x7fab9455eac0>, '__init__': <function Foo.__init__ at 0x7fab9455e200>, '__classcell__': <cell at 0x7fab97e8f910: empty>}\n", | |
"<class '__main__.Foo'>\n", | |
"Initializing class Foo\n", | |
"About to invoke class constructor with args (69,) {}\n", | |
"__new__\n", | |
"__init__\n", | |
"<__main__.Foo object at 0x7fab7df624d0>\n", | |
"About to invoke class constructor with args (1,) {}\n", | |
"__new__\n", | |
"__init__\n" | |
] | |
} | |
], | |
"source": [ | |
"class Foo(metaclass=AnotherMetaclass):\n", | |
" attr = \"asd\"\n", | |
" \n", | |
" def __new__(cls, val):\n", | |
" print(\"__new__\")\n", | |
" return super().__new__(cls)\n", | |
" \n", | |
" def __init__(cls, val):\n", | |
" print(\"__init__\")\n", | |
" \n", | |
"print(Foo(69))\n", | |
"f = Foo(1)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"As a matter of fact we could also define the metaclass for a metaclass and override `__call__()`, though that makes things very messy and we won't try." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "slide" | |
} | |
}, | |
"source": [ | |
"## What about `self`?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Why is Python so `self`ish?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"Let's start by learning that no, there's nothing special about the name \"self\" in and of it*self*." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 56, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "-" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"<__main__.Lol object at 0x7fab7df96f90>\n", | |
"<__main__.Lol object at 0x7fab7df96f90>\n" | |
] | |
} | |
], | |
"source": [ | |
"class Lol:\n", | |
" def meth(amphetamine):\n", | |
" print(amphetamine)\n", | |
" def method(self):\n", | |
" print(self)\n", | |
"\n", | |
"lol = Lol()\n", | |
"lol.meth()\n", | |
"lol.method()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"What is indeed special is the position, the first argument.\n", | |
"\n", | |
"Let's play a bit." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 60, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"I am an instance method: <__main__.Lol object at 0x7fab9835ea50>\n", | |
"I'm a lonely sad function :(\n" | |
] | |
} | |
], | |
"source": [ | |
"def external_method(self = None):\n", | |
" if self:\n", | |
" print(\"I am an instance method:\", self)\n", | |
" else:\n", | |
" print(\"I'm a lonely sad function :(\")\n", | |
"\n", | |
"# New instance\n", | |
"Lol.external_method = external_method\n", | |
"new_lol = Lol()\n", | |
"new_lol.external_method()\n", | |
"\n", | |
"# Old instance\n", | |
"lol.external_method = external_method\n", | |
"lol.external_method()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"Why does only one of them get the automatic `self` argument?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"## Descriptor protocol" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 61, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"I am an instance method: <__main__.Lol object at 0x7fab7df96f90>\n" | |
] | |
} | |
], | |
"source": [ | |
"lol.external_method = external_method.__get__(lol, type(lol))\n", | |
"lol.external_method()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "fragment" | |
} | |
}, | |
"source": [ | |
"What's this cursed syntax?" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"In short, when a class instance attribute is retrieved, it gives a chance to the retrieved data to modify the retrieval process." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"The magic is all within `__getattribute__()`, the hidden method that, given an object and a name, returns an attribute with that name." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"The default `__getattribute__()` of objects works somewhat like this:\n", | |
"* If `name` is in the the dictionary of **instance** attributes\n", | |
" * Return it\n", | |
"* For each base class in the *method resolution order*\n", | |
" * If `name` is in the the dictionary of **class** attributes\n", | |
" * If it implements the descriptor protocol:\n", | |
" * Ask it what it wants to be returned\n", | |
" * Else: Return it" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"Let's write our own makeshift method" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 63, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"class DrunkMethod:\n", | |
" def __init__(self, bound_instance = None):\n", | |
" self.bound_instance = bound_instance\n", | |
" \n", | |
" def __get__(self, instance, inst_type = None):\n", | |
" return type(self)(instance)\n", | |
" \n", | |
" def __call__(self):\n", | |
" if self.bound_instance:\n", | |
" print(f\"I'm a method of {self.bound_instance}\")\n", | |
" else:\n", | |
" print(\"I'm a standalone function\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 64, | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"I'm a standalone function\n", | |
"I'm a method of <__main__.MyClass object at 0x7fab7df438d0>\n" | |
] | |
} | |
], | |
"source": [ | |
"class MyClass:\n", | |
" method = DrunkMethod()\n", | |
" \n", | |
"MyClass.method()\n", | |
"\n", | |
"i = MyClass()\n", | |
"i.method()" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"source": [ | |
"Let's see another useful use of descriptors" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 81, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"class LoggedAttr:\n", | |
" def __init__(self, default_value = None):\n", | |
" self.default = default_value\n", | |
" self.name = None\n", | |
" \n", | |
" def __set_name__(self, clazz, attr_name):\n", | |
" print(f\"LoggedAttr was assigned in {clazz} with name '{attr_name}'\")\n", | |
" self.name = attr_name\n", | |
"\n", | |
" def __get__(self, inst, inst_ty = None):\n", | |
" print(f\"Obtaining '{self.name}' of {inst}\")\n", | |
" return getattr(inst, f\"_{self.name}\", self.default)\n", | |
" \n", | |
" def __set__(self, inst, value):\n", | |
" print(f\"Setting '{self.name}' of {inst} to {value}\")\n", | |
" setattr(inst, f\"_{self.name}\", value)\n", | |
" \n", | |
" def __delete__(self, inst):\n", | |
" print(f\"Destroying '{self.name}' in {inst}\")\n", | |
" delattr(inst, f\"_{self.name}\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 83, | |
"metadata": { | |
"scrolled": true, | |
"slideshow": { | |
"slide_type": "subslide" | |
} | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"LoggedAttr was assigned in <class '__main__.MyClass'> with name 'field'\n", | |
"Obtaining 'field' of <__main__.MyClass object at 0x7fab97e57b90>\n", | |
"Setting 'field' of <__main__.MyClass object at 0x7fab97e57b90> to bar\n", | |
"Destroying 'field' in <__main__.MyClass object at 0x7fab97e57b90>\n" | |
] | |
} | |
], | |
"source": [ | |
"class MyClass:\n", | |
" field = LoggedAttr(\"foo\") # Descriptor instance assigned to the class\n", | |
"\n", | |
"i = MyClass()\n", | |
"v = i.field\n", | |
"i.field = \"bar\"\n", | |
"del i.field" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"The end." | |
] | |
} | |
], | |
"metadata": { | |
"celltoolbar": "Slideshow", | |
"kernelspec": { | |
"display_name": "Python 3 (ipykernel)", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.11.3" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 4 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment