diff --git a/README.md b/README.md index 21eb21f..742f65b 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,10 @@ For information on the training, see the website 1. [Contributing](CONTRIBUTING.md): information on how to contribute to this repository. 1. docs: directory containing the website for this repository. + +## Contributors + +* Geert Jan Bex ([geertjan.bex@uhasselt.be](mailto:geertjan.bex@uhasselt.be)), + Hasselt University/University of Leuven +* Mustafa Dikmen, University of Leuven + * correcting typos and pointing out various mistakes \ No newline at end of file diff --git a/environment.yml b/environment.yml index ad8da8c..8ed3e97 100644 --- a/environment.yml +++ b/environment.yml @@ -6,7 +6,7 @@ dependencies: - jupyterlab - flake8 - pytest - - python=3.9 + - python=3.10 - mypy - pylint - matplotlib diff --git a/python_software_engineering.pptx b/python_software_engineering.pptx index 556e20c..0f6bcaf 100644 Binary files a/python_software_engineering.pptx and b/python_software_engineering.pptx differ diff --git a/source-code/README.md b/source-code/README.md index eb6e98c..0991213 100644 --- a/source-code/README.md +++ b/source-code/README.md @@ -26,3 +26,6 @@ was used to develop it. `pytest`. 1. `oo_vs_functional.ipynb`: comparing object-oriented approach to functional approach, including coroutines. +1. `metaclasses`: illustration of the use of metaclasses in Python. +1. `data-structures`: illustration of Python and standard library data + structures. diff --git a/source-code/data-structures/README.md b/source-code/data-structures/README.md new file mode 100644 index 0000000..ba56bb2 --- /dev/null +++ b/source-code/data-structures/README.md @@ -0,0 +1,11 @@ +# Data structures + +Python as well as its standard library have many data structures that help you +write elegant and efficient code. Here you will soe examples. + + +## What is it? + +1. `defaultdict.ipynb`: Jupyter notebook that illustrates the use of + `collections.defaultdict` with a default factory for a Python `list` and + a `dataclasss`. diff --git a/source-code/data-structures/defaultdict.ipynb b/source-code/data-structures/defaultdict.ipynb new file mode 100644 index 0000000..0671f35 --- /dev/null +++ b/source-code/data-structures/defaultdict.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2462a390-11a0-4d96-be42-a215460ee554", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4012d44e-1e7f-488d-bb19-d40466a0b800", + "metadata": {}, + "outputs": [], + "source": [ + "from collections import defaultdict\n", + "from dataclasses import dataclass" + ] + }, + { + "cell_type": "markdown", + "id": "62c1100f-d14c-4773-a87d-9b7935d55dcd", + "metadata": {}, + "source": [ + "# defaultdict" + ] + }, + { + "cell_type": "markdown", + "id": "771384fa-293a-4f00-94f9-0c5d28f19ed5", + "metadata": {}, + "source": [ + "The `defaultdict` class is quite convenient since you don't have to test whether a key is already in a dictionary. If it is not, a factory is used to create an initial entry." + ] + }, + { + "cell_type": "markdown", + "id": "f3951a33-b671-4603-8349-61c1641ae405", + "metadata": {}, + "source": [ + "## Simple example" + ] + }, + { + "cell_type": "markdown", + "id": "a2630300-45fd-4d51-8a05-a05bad9ee9d6", + "metadata": {}, + "source": [ + "Consider a dictionary that has lists as values. If a key is not in the dictionary, it should be initialized with an empty list. The factory function for an empty list is the `list` function." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "67799112-3eb0-4410-b260-5c4646adffa6", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "multiples = defaultdict(list)" + ] + }, + { + "cell_type": "markdown", + "id": "556ecf15-3003-49f8-aa9b-e9f3998f2855", + "metadata": {}, + "source": [ + "Now we can add data to the dictionary, for each integer between 0 and 20, we check whether the number is divisible by 2, 3, 4, or 5, and append it to the list that is the value for the divisor (which is the key)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "34147d51-a265-46b8-9e22-10b81460c006", + "metadata": {}, + "outputs": [], + "source": [ + "for number in range(20):\n", + " for divisor in range(2, 6):\n", + " if number % divisor == 0:\n", + " multiples[divisor].append(number)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d533abb9-7fcc-4ddf-895b-a13a71af138a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defaultdict(list,\n", + " {2: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18],\n", + " 3: [0, 3, 6, 9, 12, 15, 18],\n", + " 4: [0, 4, 8, 12, 16],\n", + " 5: [0, 5, 10, 15]})" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "multiples" + ] + }, + { + "cell_type": "markdown", + "id": "4fb29e3d-4264-4030-b933-b101c115d923", + "metadata": {}, + "source": [ + "## Arbitrary classes" + ] + }, + { + "cell_type": "markdown", + "id": "667ee4ca-6b2f-4ec0-823e-beef80dbbe93", + "metadata": {}, + "source": [ + "`defaultdict` also supports arbitrary objects as default values. Consider the following dataclass that represents a person." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0d049fe8-5b12-4c35-a730-1ddb47db55e8", + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class Person:\n", + " first_name: str = None\n", + " last_name: str = None\n", + " age: int = None" + ] + }, + { + "cell_type": "markdown", + "id": "928778d2-6fd8-4662-bb31-857e3a87f2c5", + "metadata": {}, + "source": [ + "The constructor is the default factory in this case." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "1eec5ce6-3d7e-4e64-abd1-86f5f92e1446", + "metadata": {}, + "outputs": [], + "source": [ + "people = defaultdict(Person)" + ] + }, + { + "cell_type": "markdown", + "id": "aa0d20b2-ef70-487f-8637-cc6060355c1f", + "metadata": {}, + "source": [ + "When a new person turns up, a default `Person` is constructed with `None` for each attribute, and one of the attributes, `first_name` is assigned to below." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6732bd91-c726-4603-b4c4-7c70fc5eae7d", + "metadata": {}, + "outputs": [], + "source": [ + "people['gjb'].first_name = 'Geert Jan'" + ] + }, + { + "cell_type": "markdown", + "id": "63b5c683-0a51-45e1-9d65-c9527e75136d", + "metadata": {}, + "source": [ + "The dictionary indeed contains a single entry, a `Person` with `first_name` initialized, and `liast_name` and `age` attributes still set to `None`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c510d857-6796-48c4-a181-9ce58828cc2f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "defaultdict(__main__.Person,\n", + " {'gjb': Person(first_name='Geert Jan', last_name=None, age=None)})" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "people" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.9.5" + }, + "toc-autonumbering": true + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source-code/design-patters/README.md b/source-code/design-patters/README.md index 8d6ed7b..810289f 100644 --- a/source-code/design-patters/README.md +++ b/source-code/design-patters/README.md @@ -8,3 +8,5 @@ Code to illustrate some design patterns in Python. can be used to change the behaviour of objects. 1. `factory_design_pattern.ipynb`: notebook illustrating how a factory class can be used to conveniently construct many objects with the same properties. + * `finite-state-parser`: illustration of object-oriented data + representation and the state pattern. diff --git a/source-code/object-orientation/finite-state-parser/.gitignore b/source-code/design-patters/finite-state-parser/.gitignore similarity index 100% rename from source-code/object-orientation/finite-state-parser/.gitignore rename to source-code/design-patters/finite-state-parser/.gitignore diff --git a/source-code/object-orientation/finite-state-parser/README.md b/source-code/design-patters/finite-state-parser/README.md similarity index 100% rename from source-code/object-orientation/finite-state-parser/README.md rename to source-code/design-patters/finite-state-parser/README.md diff --git a/source-code/object-orientation/finite-state-parser/bad_data.txt b/source-code/design-patters/finite-state-parser/bad_data.txt similarity index 100% rename from source-code/object-orientation/finite-state-parser/bad_data.txt rename to source-code/design-patters/finite-state-parser/bad_data.txt diff --git a/source-code/object-orientation/finite-state-parser/basic_fs_parser.py b/source-code/design-patters/finite-state-parser/basic_fs_parser.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/basic_fs_parser.py rename to source-code/design-patters/finite-state-parser/basic_fs_parser.py diff --git a/source-code/object-orientation/finite-state-parser/block.py b/source-code/design-patters/finite-state-parser/block.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/block.py rename to source-code/design-patters/finite-state-parser/block.py diff --git a/source-code/object-orientation/finite-state-parser/block_generator.py b/source-code/design-patters/finite-state-parser/block_generator.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/block_generator.py rename to source-code/design-patters/finite-state-parser/block_generator.py diff --git a/source-code/object-orientation/finite-state-parser/data_non_typed.txt b/source-code/design-patters/finite-state-parser/data_non_typed.txt similarity index 100% rename from source-code/object-orientation/finite-state-parser/data_non_typed.txt rename to source-code/design-patters/finite-state-parser/data_non_typed.txt diff --git a/source-code/object-orientation/finite-state-parser/data_typed.txt b/source-code/design-patters/finite-state-parser/data_typed.txt similarity index 100% rename from source-code/object-orientation/finite-state-parser/data_typed.txt rename to source-code/design-patters/finite-state-parser/data_typed.txt diff --git a/source-code/object-orientation/finite-state-parser/fs_parser.py b/source-code/design-patters/finite-state-parser/fs_parser.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/fs_parser.py rename to source-code/design-patters/finite-state-parser/fs_parser.py diff --git a/source-code/object-orientation/finite-state-parser/non_typed_fs_parser.py b/source-code/design-patters/finite-state-parser/non_typed_fs_parser.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/non_typed_fs_parser.py rename to source-code/design-patters/finite-state-parser/non_typed_fs_parser.py diff --git a/source-code/object-orientation/finite-state-parser/oo_fs_parser.py b/source-code/design-patters/finite-state-parser/oo_fs_parser.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/oo_fs_parser.py rename to source-code/design-patters/finite-state-parser/oo_fs_parser.py diff --git a/source-code/object-orientation/finite-state-parser/parser.py b/source-code/design-patters/finite-state-parser/parser.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/parser.py rename to source-code/design-patters/finite-state-parser/parser.py diff --git a/source-code/object-orientation/finite-state-parser/parser_errors.py b/source-code/design-patters/finite-state-parser/parser_errors.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/parser_errors.py rename to source-code/design-patters/finite-state-parser/parser_errors.py diff --git a/source-code/object-orientation/finite-state-parser/pyparsing_block_parser.py b/source-code/design-patters/finite-state-parser/pyparsing_block_parser.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/pyparsing_block_parser.py rename to source-code/design-patters/finite-state-parser/pyparsing_block_parser.py diff --git a/source-code/object-orientation/finite-state-parser/pyparsing_block_parser_script.py b/source-code/design-patters/finite-state-parser/pyparsing_block_parser_script.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/pyparsing_block_parser_script.py rename to source-code/design-patters/finite-state-parser/pyparsing_block_parser_script.py diff --git a/source-code/object-orientation/finite-state-parser/simple_block.py b/source-code/design-patters/finite-state-parser/simple_block.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/simple_block.py rename to source-code/design-patters/finite-state-parser/simple_block.py diff --git a/source-code/object-orientation/finite-state-parser/simple_parser.py b/source-code/design-patters/finite-state-parser/simple_parser.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/simple_parser.py rename to source-code/design-patters/finite-state-parser/simple_parser.py diff --git a/source-code/object-orientation/finite-state-parser/struct_fs_parser.py b/source-code/design-patters/finite-state-parser/struct_fs_parser.py similarity index 100% rename from source-code/object-orientation/finite-state-parser/struct_fs_parser.py rename to source-code/design-patters/finite-state-parser/struct_fs_parser.py diff --git a/source-code/functional-programming/README.md b/source-code/functional-programming/README.md index a281182..67930a2 100644 --- a/source-code/functional-programming/README.md +++ b/source-code/functional-programming/README.md @@ -4,3 +4,5 @@ Some examples of a functional programming style in Python. ## What is it? 1. `functional_programming_style.ipynb`: notebook explaining a functional programming style in Python with examples. +1. `sieve_of_eratosthenes.ipynb`: a lazy implementation of the sieve of + Eratosthenes using generators. diff --git a/source-code/functional-programming/sieve_of_eratosthenes.ipynb b/source-code/functional-programming/sieve_of_eratosthenes.ipynb new file mode 100644 index 0000000..5296f4d --- /dev/null +++ b/source-code/functional-programming/sieve_of_eratosthenes.ipynb @@ -0,0 +1,177 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1be55c27-90c2-42e9-ae9d-ace0c148d609", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "2535d817-9d04-4830-a43c-f33412031e1f", + "metadata": {}, + "outputs": [], + "source": [ + "from itertools import count" + ] + }, + { + "cell_type": "markdown", + "id": "24c47c91-50e1-41c7-9be1-88df29f01aae", + "metadata": {}, + "source": [ + "# Sieve of Eratosthenes" + ] + }, + { + "cell_type": "markdown", + "id": "a6e0e405-e8c9-4579-a756-ba9bb938c9d3", + "metadata": {}, + "source": [ + "The sieve of Eratosthenes is an ancient algorithm to determine prime numbers. Consider an imaginary list of all prime numbers larger than 1. The first number is 2, and it is prime. Now strike all the other even numbers from the list, since they can't be prime. The next remaining number in the list is 3, so that is prime, but all numbers divisible by 3 can't be prime, strike them from the list. Repeat until you run of of time or get terminally bored." + ] + }, + { + "cell_type": "markdown", + "id": "11557a97-fab5-4a24-ae17-73d691e2e9a0", + "metadata": {}, + "source": [ + "Consider a generator that lazily produces numbers. Now we can construct another generator that uses that first generator, but filters out some numbers using a Boolean condition. If we start with a generator that produces all odd numbers larger than 3, the next iterator can filter out all the multiples of 3, the one based on that all the multiples of 5, and so on." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e072d0e6-efb8-4f31-83ee-b08006b7294f", + "metadata": {}, + "outputs": [], + "source": [ + "def eratosthenes_sieve(numbers):\n", + " prime = next(numbers)\n", + " yield prime\n", + " yield from (number for number in numbers if number % prime != 0)" + ] + }, + { + "cell_type": "markdown", + "id": "11bdddec-a20f-4904-9134-5de74d2813aa", + "metadata": {}, + "source": [ + "The first number produced by the generator `numbers` is always a prime, so it is returned. Next a new iterator is constructed by yields all numbers, except those divisible by the prime number we've just returned." + ] + }, + { + "cell_type": "markdown", + "id": "27a70870-1f34-48cf-9b23-48b8afb93aa5", + "metadata": {}, + "source": [ + "As a last step, we write a function that yields 2, and then all the odd prime numbers." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "85eaccfd-c2d3-47c8-8241-9d7e7a810943", + "metadata": {}, + "outputs": [], + "source": [ + "def primes():\n", + " yield 2\n", + " yield from eratosthenes_sieve(count(start=3, step=2))" + ] + }, + { + "cell_type": "markdown", + "id": "74f29fa4-10c4-4bf5-bebb-8ddc16689fb2", + "metadata": {}, + "source": [ + "Use this to generator all prime numbers less than 100." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "d2b0242f-1233-451d-9248-5a65eea1e9e5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n", + "3\n", + "5\n", + "7\n", + "11\n", + "13\n", + "17\n", + "19\n", + "23\n", + "25\n", + "29\n", + "31\n", + "35\n", + "37\n", + "41\n", + "43\n", + "47\n", + "49\n", + "53\n", + "55\n", + "59\n", + "61\n", + "65\n", + "67\n", + "71\n", + "73\n", + "77\n", + "79\n", + "83\n", + "85\n", + "89\n", + "91\n", + "95\n", + "97\n" + ] + } + ], + "source": [ + "for prime in primes():\n", + " if prime >= 100: break\n", + " print(prime)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54735c93-da12-4d34-aa82-f448eb9586ad", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "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.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source-code/metaclasses/README.md b/source-code/metaclasses/README.md new file mode 100644 index 0000000..9a9e57a --- /dev/null +++ b/source-code/metaclasses/README.md @@ -0,0 +1,15 @@ +# Metaclasses + +Python supports metaclasses, i.e., classes that control the creation of other +classes. + +Although this feature should not be overused, it can be useful in some +circumstances. + +See the following [article](https://medium.com/fintechexplained/advanced-python-metaprogramming-980da1be0c7d) for a discussion. + + +## What is it? + +1. `metaclasses.ipynb`: Jupyter notebook illustrating using a metaclass to + verify the correct implementation of a mix-in. diff --git a/source-code/metaclasses/metaclasses.ipynb b/source-code/metaclasses/metaclasses.ipynb new file mode 100644 index 0000000..e27077c --- /dev/null +++ b/source-code/metaclasses/metaclasses.ipynb @@ -0,0 +1,195 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "72694516-dbb4-47dc-bb49-cf0832d2eb2b", + "metadata": {}, + "source": [ + "Metaclasses can be quite useful in some circumstances. A metaclass serves as a constructor for classes and can be used for validation at defintion time.\n", + "\n", + "Consider the following metaclass: it will check whether the class has a `_sound` attribute, if not, it will fail upon defintion. It will also add a `make_sound` method, acting similar to a mix-in." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "a2c0fb42-c8b7-4e7c-9044-f0a543a11a6c", + "metadata": {}, + "outputs": [], + "source": [ + "class SoundMeta(type):\n", + " \n", + " def __new__(cls, what, bases=None, dict=None):\n", + " if '_sound' not in dict:\n", + " raise Exception('no _sound attribute defined')\n", + " new_dict = dict.copy()\n", + " new_dict['make_sound'] = lambda self: f'say {self._sound}'\n", + " return type.__new__(cls, what, bases, new_dict)" + ] + }, + { + "cell_type": "markdown", + "id": "356b35e2-b534-44d1-bea6-67675cc0bdbb", + "metadata": {}, + "source": [ + "Note that the `SoundMeta` class has `type` as a base class, to `SoundMeta` is a class. It defines its own `__new__` function to control object creation, which calls the parent's (i.e., `type`'s) `__new__` method with the modified dictionary." + ] + }, + { + "cell_type": "markdown", + "id": "64654746-fc9f-4448-b26d-1887b391fd71", + "metadata": {}, + "source": [ + "The `Dog` class has a `sound` attribute, and defines some attributes and methods of its own." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "73852058-22e2-4036-86ef-4e205a12bef7", + "metadata": {}, + "outputs": [], + "source": [ + "class Dog(metaclass=SoundMeta):\n", + " _sound: str = 'woof'\n", + " \n", + " def __init__(self, name):\n", + " self._name = name\n", + " \n", + " @property\n", + " def name(self):\n", + " return self.name" + ] + }, + { + "cell_type": "markdown", + "id": "696ff05b-273d-4dd3-ab2c-ac5a219cdee3", + "metadata": {}, + "source": [ + "We can instantiate a particular `Dog` and call its `make_sound()` method." + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "678a6d7e-f165-4183-a183-001c0a333a54", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'say woof'" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dog = Dog('felix')\n", + "dog.make_sound()" + ] + }, + { + "cell_type": "markdown", + "id": "bfbfbd5f-b2d4-4aa2-acf4-b331ce86a768", + "metadata": {}, + "source": [ + "The `Bell` class also has `SoundMeta` as metaclass." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "862b88a9-c467-43d3-975f-70aac7cdc330", + "metadata": {}, + "outputs": [], + "source": [ + "class Bell(metaclass=SoundMeta):\n", + " _sound: str = 'dong'" + ] + }, + { + "cell_type": "markdown", + "id": "ab5b88a3-1374-4980-aa97-4a33dc867c75", + "metadata": {}, + "source": [ + "It too can be used to instantiate objects that make a sound." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "be700599-60bc-490a-8fbb-4bdae3cee06f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'say dong'" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "big_ben = Bell()\n", + "big_ben.make_sound()" + ] + }, + { + "cell_type": "markdown", + "id": "31cebd32-f4df-4a5b-9516-8b2a7a5ffaa3", + "metadata": {}, + "source": [ + "If you try to create a class that has no `_sound` attribute, the class definition will result in an error." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "0f537499-6a88-4ca6-a9d8-1fad1ef5272c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "no _sound attribute defined\n" + ] + } + ], + "source": [ + "try:\n", + " class Phone(metaclass=SoundMeta):\n", + " pass\n", + "except Exception as e:\n", + " print(e)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.9.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source-code/object-orientation/README.md b/source-code/object-orientation/README.md index 0f89a86..5bbd3ec 100644 --- a/source-code/object-orientation/README.md +++ b/source-code/object-orientation/README.md @@ -19,6 +19,4 @@ Some examples of object-oriented programming in Python. * `multiple_inheritance.ipynb`: notebook illustrating some aspects of multiple inheritance in Python. * `mix-ins`: illustration of mix-ins. - * `finite-state-parser`: illustration of object-oriented data - representation. * `attr_intro.ipynb`: illustration of how to use the `attrs` package. diff --git a/source-code/oo_vs_functional.ipynb b/source-code/oo_vs_functional.ipynb index 3b3db32..ecf7e17 100644 --- a/source-code/oo_vs_functional.ipynb +++ b/source-code/oo_vs_functional.ipynb @@ -16,12 +16,13 @@ "outputs": [], "source": [ "from collections import Counter\n", - "from functools import wraps\n", + "from functools import wraps, reduce\n", "import heapq\n", "import math\n", "from math import exp, floor, log\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", + "import operator\n", "import random" ] }, @@ -156,7 +157,22 @@ " float\n", " current standard deviation, None if less than two data values'''\n", " if self.n > 1:\n", - " return math.sqrt((self._sum2 - self._sum**2/self.n)/(self.n - 1))" + " return math.sqrt((self._sum2 - self._sum**2/self.n)/(self.n - 1))\n", + " \n", + " def __repr__(self):\n", + " '''return a texutal representation of the object\n", + " \n", + " Returns\n", + " -------\n", + " str\n", + " string representation of the object\n", + " '''\n", + " if self.n == 0:\n", + " return f'mean = N/A, stddev = N/A, n = {self.n}'\n", + " elif self.n == 1:\n", + " return f'mean = {self.mean}, stddev = N/A, n = {self.n}'\n", + " else:\n", + " return f'mean = {self.mean}, stddev = {self.stddev}, n = {self.n}'" ] }, { @@ -178,11 +194,11 @@ "output_type": "stream", "text": [ "0 None None\n", - "1 1.0 None\n", - "2 1.5 0.7071067811865476\n", - "3 2.0 1.0\n", - "4 2.5 1.2909944487358056\n", - "5 3.0 1.5811388300841898\n" + "mean = 1.0, stddev = N/A, n = 1\n", + "mean = 1.5, stddev = 0.7071067811865476, n = 2\n", + "mean = 2.0, stddev = 1.0, n = 3\n", + "mean = 2.5, stddev = 1.2909944487358056, n = 4\n", + "mean = 3.0, stddev = 1.5811388300841898, n = 5\n" ] } ], @@ -191,7 +207,7 @@ "print(stats.n, stats.mean, stats.stddev)\n", "for value in range(1, 6):\n", " stats.add(value)\n", - " print(stats.n, stats.mean, stats.stddev)" + " print(stats)" ] }, { @@ -212,20 +228,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "odd stats -> 1 1.0 None\n", - "even stats -> 0 None None\n", - "odd stats -> 1 1.0 None\n", - "even stats -> 1 2.0 None\n", - "odd stats -> 2 2.0 1.4142135623730951\n", - "even stats -> 1 2.0 None\n", - "odd stats -> 2 2.0 1.4142135623730951\n", - "even stats -> 2 3.0 1.4142135623730951\n", - "odd stats -> 3 3.0 2.0\n", - "even stats -> 2 3.0 1.4142135623730951\n", - "odd stats -> 3 3.0 2.0\n", - "even stats -> 3 4.0 2.0\n", - "odd stats -> 4 4.0 2.581988897471611\n", - "even stats -> 3 4.0 2.0\n" + "odd stats -> mean = 1.0, stddev = N/A, n = 1\n", + "even stats -> mean = N/A, stddev = N/A, n = 0\n", + "odd stats -> mean = 1.0, stddev = N/A, n = 1\n", + "even stats -> mean = 2.0, stddev = N/A, n = 1\n", + "odd stats -> mean = 2.0, stddev = 1.4142135623730951, n = 2\n", + "even stats -> mean = 2.0, stddev = N/A, n = 1\n", + "odd stats -> mean = 2.0, stddev = 1.4142135623730951, n = 2\n", + "even stats -> mean = 3.0, stddev = 1.4142135623730951, n = 2\n", + "odd stats -> mean = 3.0, stddev = 2.0, n = 3\n", + "even stats -> mean = 3.0, stddev = 1.4142135623730951, n = 2\n", + "odd stats -> mean = 3.0, stddev = 2.0, n = 3\n", + "even stats -> mean = 4.0, stddev = 2.0, n = 3\n", + "odd stats -> mean = 4.0, stddev = 2.581988897471611, n = 4\n", + "even stats -> mean = 4.0, stddev = 2.0, n = 3\n" ] } ], @@ -236,8 +252,8 @@ " even_stats.add(value)\n", " else:\n", " odd_stats.add(value)\n", - " print('odd stats -> ', odd_stats.n, odd_stats.mean, odd_stats.stddev)\n", - " print('even stats -> ', even_stats.n, even_stats.mean, even_stats.stddev)" + " print('odd stats -> ', odd_stats)\n", + " print('even stats -> ', even_stats)" ] }, { @@ -672,6 +688,267 @@ " print('odd stats -> ', odd_stats(value))" ] }, + { + "cell_type": "markdown", + "id": "0383b8ab-1820-4b47-a40b-3a26a42dbd67", + "metadata": {}, + "source": [ + "But is this really a functional approach? Strictly speaking, no. Functional programming is supposed to be side-effect free, but our function in fact has state that evolves over time.\n", + "\n", + "The following class forms the basis for a real functional implementation." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "5e97514c-d7a6-4b24-aec0-b78d261ad09b", + "metadata": {}, + "outputs": [], + "source": [ + "class FunctionalStats:\n", + " \n", + " def __init__(self, sum_value: float=0.0, sum2: float=0.0, n: int=0):\n", + " '''constructor for Stats objects\n", + " \n", + " Params\n", + " ------\n", + " Either none, which initializes an object that has seen no data yet, or\n", + " \n", + " sum_value: float\n", + " the sum of the values up to this point\n", + " sum2: float\n", + " the sum of the squares of the values up to this point\n", + " n: int\n", + " the number of values up to this point\n", + " '''\n", + " self._sum = sum_value\n", + " self._sum2 = sum2\n", + " self._n = n\n", + " \n", + " @property\n", + " def n(self):\n", + " '''Number of values seen by this object\n", + " \n", + " Returns\n", + " -------\n", + " int\n", + " number of data values seen so far\n", + " '''\n", + " return self._n\n", + " \n", + " @property\n", + " def mean(self):\n", + " '''Mean value of the data values seen so far\n", + " \n", + " Returns\n", + " -------\n", + " float\n", + " mean value\n", + " \n", + " Raises\n", + " ------\n", + " ValueError\n", + " Exception when the number of values is less than 1\n", + " '''\n", + " if self._n < 1:\n", + " raise ValueError('at least one data value required for mean')\n", + " return self._sum/self._n\n", + " \n", + " @property\n", + " def stddev(self):\n", + " '''Standard deviation of the data values seen so far\n", + " \n", + " Returns\n", + " -------\n", + " float\n", + " standard deviation\n", + "\n", + " Raises\n", + " ------\n", + " ValueError\n", + " Exception when the number of values is less than 1\n", + " \n", + " '''\n", + " if self._n < 2:\n", + " raise ValueError('at least two data values required for standard deviation')\n", + " return math.sqrt((self._sum2 - self._sum**2/self._n)/(self._n - 1))\n", + " \n", + " def __add__(self, value: float):\n", + " '''functional operator, creates a new Stats object when a value is added\n", + " called as stats + value\n", + " \n", + " Params\n", + " ------\n", + " value: float\n", + " new data value to be added to the statistics\n", + " \n", + " Returns\n", + " -------\n", + " Stats\n", + " a new Stats object that includes the value just added\n", + " '''\n", + " return FunctionalStats(sum_value=self._sum + value, sum2=self._sum2 + value**2, n=self._n + 1)\n", + " \n", + " def __iadd__(self, value: float):\n", + " '''non-functional operator, updates the Stats object when a value is added\n", + " \n", + " Params\n", + " ------\n", + " value: float\n", + " new data value to be added to the statistics\n", + " \n", + " Returns\n", + " -------\n", + " Stats\n", + " updated Stats object including the value just added, called as\n", + " stats += value\n", + " '''\n", + " self._sum += value\n", + " self._sum2 += value**2\n", + " self._n += 1\n", + " return self\n", + " \n", + " def __repr__(self):\n", + " '''return a texutal representation of the object\n", + " \n", + " Returns\n", + " -------\n", + " str\n", + " string representation of the object\n", + " '''\n", + " if self.n == 0:\n", + " return f'mean = N/A, stddev = N/A, n = {self.n}'\n", + " elif self.n == 1:\n", + " return f'mean = {self.mean}, stddev = N/A, n = {self.n}'\n", + " else:\n", + " return f'mean = {self.mean}, stddev = {self.stddev}, n = {self.n}'" + ] + }, + { + "cell_type": "markdown", + "id": "ca890ab0-ac7c-4bac-ad88-5bf35b3de271", + "metadata": {}, + "source": [ + "To illustrate that using the `+` operator is truly side-effect free, we can sotre all the intermediate objects n a list, and print them when done." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "6ec62835-ec1b-42e7-980b-dabc1efb16a4", + "metadata": {}, + "outputs": [], + "source": [ + "values = [3.1, 5.2, 7.3]" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "54875dc7-5af3-4346-89da-ccd797ca1b44", + "metadata": {}, + "outputs": [], + "source": [ + "all_stats = [FunctionalStats()]\n", + "for value in values:\n", + " all_stats.append(all_stats[-1] + value)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "e8adc830-7e6f-4521-af07-776e7a03f6c3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mean = N/A, stddev = N/A, n = 0\n", + "mean = 3.1, stddev = N/A, n = 1\n", + "mean = 4.15, stddev = 1.4849242404917493, n = 2\n", + "mean = 5.2, stddev = 2.0999999999999974, n = 3\n" + ] + } + ], + "source": [ + "for stats in all_stats:\n", + " print(stats)" + ] + }, + { + "cell_type": "markdown", + "id": "4aff29b4-fdbe-409d-b80e-f113caff6d45", + "metadata": {}, + "source": [ + "On the other hand, using the `+=` operator which is non-functional shows that indeed the object is updated." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "e5c939ec-5afa-47c4-bb6d-bd3fc218a061", + "metadata": {}, + "outputs": [], + "source": [ + "all_stats = [FunctionalStats()]\n", + "for value in values:\n", + " stats = all_stats[-1]\n", + " stats += value\n", + " all_stats.append(stats)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ff7fa2db-e6a8-4fe0-b396-aa22e92dd721", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mean = 5.2, stddev = 2.0999999999999974, n = 3\n", + "mean = 5.2, stddev = 2.0999999999999974, n = 3\n", + "mean = 5.2, stddev = 2.0999999999999974, n = 3\n", + "mean = 5.2, stddev = 2.0999999999999974, n = 3\n" + ] + } + ], + "source": [ + "for stats in all_stats:\n", + " print(stats)" + ] + }, + { + "cell_type": "markdown", + "id": "18c8630e-9006-4c2b-aaa0-37468bfc57ae", + "metadata": {}, + "source": [ + "Note that is now trivial to use this `Stats` object in a reduction." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "dae60959-7900-49dd-81f3-e38f4f607a78", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "mean = 5.2, stddev = 2.0999999999999974, n = 3" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "reduce(operator.add, values, FunctionalStats())" + ] + }, { "cell_type": "markdown", "id": "chief-monkey", @@ -690,7 +967,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 21, "id": "packed-marijuana", "metadata": {}, "outputs": [], @@ -700,7 +977,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 22, "id": "ongoing-showcase", "metadata": {}, "outputs": [ @@ -708,7 +985,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "454 ms ± 1.62 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "307 ms ± 2.92 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], @@ -721,7 +998,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 23, "id": "bridal-sender", "metadata": {}, "outputs": [ @@ -729,7 +1006,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "483 ms ± 12.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "486 ms ± 6.41 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], @@ -744,7 +1021,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 24, "id": "fabulous-standard", "metadata": {}, "outputs": [ @@ -752,7 +1029,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "467 ms ± 3.95 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "468 ms ± 6.78 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], @@ -764,29 +1041,105 @@ ] }, { - "cell_type": "markdown", - "id": "greatest-ambassador", + "cell_type": "code", + "execution_count": 25, + "id": "551b9dce-5f8b-41dc-9e1a-e4ac604d770a", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "587 ms ± 8.13 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], "source": [ - "## Conclusion" + "%%timeit\n", + "stats = FunctionalStats()\n", + "for value in values:\n", + " stats = stats + value" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "2511caf8-d7cb-4990-9cd9-03c360528644", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "591 ms ± 2.08 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit reduce(operator.add, values, FunctionalStats())" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "c8edc1e2-cac5-4f15-a688-fd9cbff918f0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "319 ms ± 1.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "stats = FunctionalStats()\n", + "for value in values:\n", + " stats += value" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "31ffb758-5e03-40d3-85e6-313e7ec8cc17", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "323 ms ± 984 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit reduce(operator.iadd, values, FunctionalStats())" ] }, { "cell_type": "markdown", - "id": "normal-tucson", + "id": "greatest-ambassador", "metadata": {}, "source": [ - "It is immediately clear that hte funcitonal approach is ridiculously easy, once you wrap your head around it. Up to that time, the object oriented approach might be more intuitive.\n", - "\n", - "Coroutines seems the hardest to do, although they are intuitevely easy to understand, the mechanics to get them to work are somewhat cumbersome. However, they have nice applications when creating data pipelines." + "## Conclusion" ] }, { "cell_type": "markdown", - "id": "oriental-transportation", + "id": "182b3e62-9778-4efa-b870-a1f3a66196d8", "metadata": {}, "source": [ - "The object oriented approach has a better performance, but only by 5 %." + "It is immediately clear that the \"not really\" funcitonal approach is ridiculously easy, once you wrap your head around it. Up to that time, the object oriented approach might be more intuitive.\n", + "\n", + "The true functional approach is definitely the slowest verion by some margin, but it is of course the cleanest since it is side-effect free.\n", + "\n", + "Coroutines seems the hardest to do, although they are intuitevely easy to understand, the mechanics to get them to work are somewhat cumbersome. However, they have nice applications when creating data pipelines.\n", + "\n", + "The object oriented approach has a better performance, but only by 5 %.\n", + "\n", + "Overloading the `+=` operator gives the best performance by quite some margin." ] }, { @@ -831,7 +1184,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 29, "id": "annual-preference", "metadata": {}, "outputs": [ @@ -867,7 +1220,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 30, "id": "homeless-poland", "metadata": {}, "outputs": [], @@ -943,7 +1296,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 31, "id": "reverse-productivity", "metadata": {}, "outputs": [ @@ -951,16 +1304,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "[3] -> 3\n", - "[3, 47] -> 25.0\n", - "[3, 30, 47] -> 30\n", - "[3, 30, 47, 48] -> 38.5\n", - "[2, 3, 30, 47, 48] -> 30\n", - "[2, 3, 9, 30, 47, 48] -> 19.5\n", - "[2, 3, 9, 30, 44, 47, 48] -> 30\n", - "[2, 3, 9, 30, 44, 47, 48, 49] -> 37.0\n", - "[2, 3, 7, 9, 30, 44, 47, 48, 49] -> 30\n", - "[2, 3, 7, 9, 18, 30, 44, 47, 48, 49] -> 24.0\n" + "[23] -> 23\n", + "[23, 29] -> 26.0\n", + "[23, 24, 29] -> 24\n", + "[23, 24, 29, 50] -> 26.5\n", + "[6, 23, 24, 29, 50] -> 24\n", + "[5, 6, 23, 24, 29, 50] -> 23.5\n", + "[5, 5, 6, 23, 24, 29, 50] -> 23\n", + "[5, 5, 6, 23, 24, 28, 29, 50] -> 23.5\n", + "[5, 5, 6, 23, 24, 28, 29, 42, 50] -> 24\n", + "[5, 5, 6, 23, 24, 28, 28, 29, 42, 50] -> 26.0\n" ] } ], @@ -1000,7 +1353,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 32, "id": "upper-diving", "metadata": {}, "outputs": [], @@ -1044,7 +1397,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 33, "id": "wrong-pavilion", "metadata": {}, "outputs": [], @@ -1071,7 +1424,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 34, "id": "approved-definition", "metadata": {}, "outputs": [ @@ -1081,7 +1434,7 @@ "252" ] }, - "execution_count": 23, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -1100,7 +1453,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 35, "id": "objective-scheduling", "metadata": {}, "outputs": [ @@ -1110,7 +1463,7 @@ "252" ] }, - "execution_count": 24, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -1121,7 +1474,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 36, "id": "convinced-visitor", "metadata": {}, "outputs": [ @@ -1131,7 +1484,7 @@ "252" ] }, - "execution_count": 25, + "execution_count": 36, "metadata": {}, "output_type": "execute_result" } @@ -1150,13 +1503,13 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 37, "id": "alike-killer", "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1173,17 +1526,17 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 38, "id": "posted-nebraska", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "(331, 458)" + "(342, 458)" ] }, - "execution_count": 27, + "execution_count": 38, "metadata": {}, "output_type": "execute_result" } @@ -1217,7 +1570,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.2" + "version": "3.9.5" } }, "nbformat": 4, diff --git a/source-code/prime_time.ipynb b/source-code/prime_time.ipynb new file mode 100644 index 0000000..a2b6776 --- /dev/null +++ b/source-code/prime_time.ipynb @@ -0,0 +1,271 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fc3b2761-8665-477a-8bfc-58bdf2ac4f7a", + "metadata": {}, + "source": [ + "# Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "47182c93-a8f7-4772-9a9c-e58a433d02ba", + "metadata": {}, + "outputs": [], + "source": [ + "import itertools\n", + "import math" + ] + }, + { + "cell_type": "markdown", + "id": "4e0adefe-f566-4a81-a0df-0b5d0c7604cf", + "metadata": {}, + "source": [ + "# Precomputed sieve" + ] + }, + { + "cell_type": "markdown", + "id": "0f0a2be6-515a-4d25-a5c8-67b9dfefe69a", + "metadata": {}, + "source": [ + "The sieve of Eratosthenes is computed and stored in a list. The overall implementation is a generator for convenience." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b6c3ba8e-e5c5-4a3a-b059-02fa0283776a", + "metadata": {}, + "outputs": [], + "source": [ + "def prime_numbers_precomputed(max_n):\n", + " sieve = [True]*(max_n + 1)\n", + " sieve[0], sieve[1] = False, False\n", + " for n in range(2, math.isqrt(max_n) + 1):\n", + " if sieve[n]:\n", + " for non_prime in range(2*n, max_n + 1, n):\n", + " sieve[non_prime] = False\n", + " yield from (n for n, is_prime in enumerate(sieve) if is_prime)" + ] + }, + { + "cell_type": "markdown", + "id": "3ad4ec7f-303b-4980-8405-db68f8e86339", + "metadata": {}, + "source": [ + "Generate prime numbers upto 50." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a1ee6b85-5af0-4f4a-9a70-e1f08d177349", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n", + "3\n", + "5\n", + "7\n", + "11\n", + "13\n", + "17\n", + "19\n", + "23\n", + "29\n", + "31\n", + "37\n", + "41\n", + "43\n", + "47\n" + ] + } + ], + "source": [ + "for prime in prime_numbers_precomputed(50):\n", + " print(prime)" + ] + }, + { + "cell_type": "markdown", + "id": "6eef26fa-d00d-4fab-a8d7-08ffc52590bd", + "metadata": {}, + "source": [ + "# Sieve using generators" + ] + }, + { + "cell_type": "markdown", + "id": "54049286-713e-48fa-a1fb-44078942d348", + "metadata": {}, + "source": [ + "Implementation using generators, entirely lazy." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ab70534d-688f-46f4-8b30-44bf1e34ab5b", + "metadata": {}, + "outputs": [], + "source": [ + "def prime_numbers():\n", + " def prime_number_sieve(numbers):\n", + " prime = next(numbers)\n", + " yield prime\n", + " yield from (n for n in numbers if n % prime != 0)\n", + " yield 2\n", + " yield from prime_number_sieve(itertools.count(3, 2))" + ] + }, + { + "cell_type": "markdown", + "id": "b54d4fd3-4e5a-4556-9bac-85da2ceb9e19", + "metadata": {}, + "source": [ + "Again, generating the prime numbers up to 50." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d67bb1fe-8e55-45b0-aace-36ab4397acb0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n", + "3\n", + "5\n", + "7\n", + "11\n", + "13\n", + "17\n", + "19\n", + "23\n", + "25\n", + "29\n", + "31\n", + "35\n", + "37\n", + "41\n", + "43\n", + "47\n", + "49\n" + ] + } + ], + "source": [ + "for prime in prime_numbers():\n", + " if prime <= 50:\n", + " print(prime)\n", + " else:\n", + " break" + ] + }, + { + "cell_type": "markdown", + "id": "edf1943a-31b2-4a4e-bf96-47ea2875b567", + "metadata": {}, + "source": [ + "# Comparing performance" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "83d6696e-b31c-4dbc-9b0c-702c9aedc4a9", + "metadata": {}, + "outputs": [], + "source": [ + "max_n = 1_000_000" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "bbea5e94-d82a-4be9-b4f4-152a8136c608", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "80.4 ms ± 2.83 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "numbers = []\n", + "for prime in prime_numbers_precomputed(max_n):\n", + " if prime <= max_n:\n", + " numbers.append(prime)\n", + " else:\n", + " break" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "351d9f27-6d04-4d70-b8e3-451d223f64a4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "46.9 ms ± 1.48 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%%timeit\n", + "numbers = []\n", + "for prime in prime_numbers():\n", + " if prime <= max_n:\n", + " numbers.append(prime)\n", + " else:\n", + " break" + ] + }, + { + "cell_type": "markdown", + "id": "1111d621-2f13-456c-ba62-a493df7fdc64", + "metadata": {}, + "source": [ + "Interestingly, the lazy implementation is faster by a substantial factor, perhaps this could be improved by using a different data representation for the precomputed sieve." + ] + } + ], + "metadata": { + "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.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/source-code/typing/README.md b/source-code/typing/README.md index ff6dd3d..ad7eaf0 100644 --- a/source-code/typing/README.md +++ b/source-code/typing/README.md @@ -23,3 +23,13 @@ Type checking can be done using [mypy](http://mypy-lang.org/index.html). which is a type error. 1. `people_incorrect.py`: code that defines a `People` class, stores some in a list with mistakes. +1. `duck_typing.py`: example code illustrating duck typing. +1. `duck_typing_wrong.py`: example code illustrating duck typing, but + with an error. +1. `typed_duck_typing.py`: example code illustrating duck typing + using type hints. +1. `typed_duck_typing_wrong.py`: example code illustrating duck typing + using type hints with an error. +1. `typed_duck_typing_false_positive.py`: example code illustrating + duck typing using type hints for which mypy 0.910 generates a + false positive. diff --git a/source-code/typing/duck_typing.py b/source-code/typing/duck_typing.py new file mode 100644 index 0000000..39d2b4a --- /dev/null +++ b/source-code/typing/duck_typing.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +import argparse + +class Duck: + + def make_sound(self): + print('quack') + + +class AlarmClock: + + def make_sound(self): + print('ring-ring') + + +def sound_repeater(sound_maker, nr_repeats): + for _ in range(nr_repeats): + sound_maker.make_sound() + + +if __name__ == '__main__': + arg_parser = argparse.ArgumentParser(description='sound maker') + arg_parser.add_argument('--type', choices=['duck', 'alarm'], + help='sound source') + arg_parser.add_argument('--n', type=int, default=1, + help='number of sounds to make') + options = arg_parser.parse_args() + sound_maker = Duck() if options.type == 'duck' else AlarmClock() + sound_repeater(sound_maker, options.n) diff --git a/source-code/typing/duck_typing_wrong.py b/source-code/typing/duck_typing_wrong.py new file mode 100644 index 0000000..76aaefa --- /dev/null +++ b/source-code/typing/duck_typing_wrong.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +import argparse + +class Duck: + + def quack(self): + print('quack') + + +class AlarmClock: + + def make_sound(self): + print('ring-ring') + + +def sound_repeater(sound_maker, nr_repeats): + for _ in range(nr_repeats): + sound_maker.make_sound() + + +if __name__ == '__main__': + arg_parser = argparse.ArgumentParser(description='sound maker') + arg_parser.add_argument('--type', choices=['duck', 'alarm'], + help='sound source') + arg_parser.add_argument('--n', type=int, default=1, + help='number of sounds to make') + options = arg_parser.parse_args() + sound_maker = Duck() if options.type == 'duck' else AlarmClock() + sound_repeater(sound_maker, options.n) diff --git a/source-code/typing/incorrect_02.py b/source-code/typing/incorrect_02.py index 8e9cfd9..46e20f4 100644 --- a/source-code/typing/incorrect_02.py +++ b/source-code/typing/incorrect_02.py @@ -10,5 +10,5 @@ def fib(n: int) -> int: return fib(n - 1) + fib(n - 2) if __name__ == '__main__': - n = int(sys.argv[1]) # type: str + n: str = int(sys.argv[1]) print(fib(n)) diff --git a/source-code/typing/typed_duck_typing.py b/source-code/typing/typed_duck_typing.py new file mode 100644 index 0000000..0bc8e3b --- /dev/null +++ b/source-code/typing/typed_duck_typing.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +import argparse +from typing import Protocol + +class SoundMaker(Protocol): + + def make_sound(self) -> None: + pass + + +class Duck: + + def make_sound(self) -> None: + print('quack') + + +class AlarmClock: + + def make_sound(self) -> None: + print('ring-ring') + + +def sound_repeater(sound_maker: SoundMaker, nr_repeats: int): + for _ in range(nr_repeats): + sound_maker.make_sound() + + +if __name__ == '__main__': + arg_parser = argparse.ArgumentParser(description='sound maker') + arg_parser.add_argument('--type', choices=['duck', 'alarm'], + help='sound source') + arg_parser.add_argument('--n', type=int, default=1, + help='number of sounds to make') + options = arg_parser.parse_args() + sound_maker: SoundMaker + if options.type == 'duck': + sound_maker = Duck() + else: + sound_maker = AlarmClock() + sound_repeater(sound_maker, options.n) diff --git a/source-code/typing/typed_duck_typing_false_positive.py b/source-code/typing/typed_duck_typing_false_positive.py new file mode 100644 index 0000000..d799e80 --- /dev/null +++ b/source-code/typing/typed_duck_typing_false_positive.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +import argparse +from typing import Protocol + +class SoundMaker(Protocol): + + def make_sound(self) -> None: + pass + + +class Duck: + + def make_sound(self) -> None: + print('quack') + + +class AlarmClock: + + def make_sound(self) -> None: + print('ring-ring') + + +def sound_repeater(sound_maker: SoundMaker, nr_repeats: int): + for _ in range(nr_repeats): + sound_maker.make_sound() + + +if __name__ == '__main__': + arg_parser = argparse.ArgumentParser(description='sound maker') + arg_parser.add_argument('--type', choices=['duck', 'alarm'], + help='sound source') + arg_parser.add_argument('--n', type=int, default=1, + help='number of sounds to make') + options = arg_parser.parse_args() + sound_maker: SoundMaker = Duck() if options.type == 'duck' else AlarmClock() + sound_repeater(sound_maker, options.n) diff --git a/source-code/typing/typed_duck_typing_wrong.py b/source-code/typing/typed_duck_typing_wrong.py new file mode 100644 index 0000000..6dfa123 --- /dev/null +++ b/source-code/typing/typed_duck_typing_wrong.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +import argparse +from typing import Protocol + +class SoundMaker(Protocol): + + def make_sound(self) -> None: + pass + + +class Duck: + + def quack(self) -> None: + print('quack') + + +class AlarmClock: + + def make_sound(self) -> None: + print('ring-ring') + + +def sound_repeater(sound_maker: SoundMaker, nr_repeats: int): + for _ in range(nr_repeats): + sound_maker.make_sound() + + +if __name__ == '__main__': + arg_parser = argparse.ArgumentParser(description='sound maker') + arg_parser.add_argument('--type', choices=['duck', 'alarm'], + help='sound source') + arg_parser.add_argument('--n', type=int, default=1, + help='number of sounds to make') + options = arg_parser.parse_args() + sound_maker: SoundMaker + if options.type == 'duck': + sound_maker = Duck() + else: + sound_maker = AlarmClock() + sound_repeater(sound_maker, options.n)