diff --git a/docs/README.md b/docs/README.md index 8dd040d..73b61fb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -65,6 +65,11 @@ from scratch. If you plan to do Python programming in a Linux or HPC environment you should be familiar with these as well. +For following along hands-on, you need +* laptop or desktop with internet access. +* a Python environment that can run Jupyter Lab if you want to use your own system; +* access to Google Colaboratory if you prefer not to install software. + ## Level diff --git a/docs/_config.yml b/docs/_config.yml index c741881..f702a7d 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1 +1,2 @@ -theme: jekyll-theme-slate \ No newline at end of file +title: "Python software development" +theme: jekyll-theme-slate diff --git a/python_software_engineering.pptx b/python_software_engineering.pptx index 934e301..d3d61e1 100644 Binary files a/python_software_engineering.pptx and b/python_software_engineering.pptx differ diff --git a/source-code/decorators/README.md b/source-code/decorators/README.md index 827c389..7bb4725 100644 --- a/source-code/decorators/README.md +++ b/source-code/decorators/README.md @@ -14,5 +14,9 @@ What is it? large. The decorated function in this example is the factorial. Also it illustrates `functools` wrap decorator to retain the wrapped function's name, docstring, etc. +1. `decorator_arguments.py`: an example of a decorator that takes arguments. + The `check_range` decorator takes a minimum and maximum value as arguments + and checks if the wrapped function's argument falls within that range, + throwing an exception if it does not. 1. `memoize.py`: an example of adding a cache to a function using a simple custom decorator, as well as `functools`'s `lru_cache` decorator. diff --git a/source-code/decorators/decorator_arguments.py b/source-code/decorators/decorator_arguments.py new file mode 100644 index 0000000..1789348 --- /dev/null +++ b/source-code/decorators/decorator_arguments.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import functools + + +def check_bounds(min_value, max_value): + '''Check bounds of a function argument + + Parameters + ---------- + min_value: float + Smallest value allowed for the function's parameter + max_value: float + Largest value allowed for the function's parameter + + Raises + ------ + ValueError: + If the argument is outside the specified bounds + ''' + def check_bounds_wrapper(_func): + @functools.wraps(_func) + def wrapper(x): + if min_value > x or x > max_value: + raise ValueError(f'argument {x} not in [{min_value}, {max_value}]') + return _func(x) + return wrapper + return check_bounds_wrapper + + +@check_bounds(min_value=-1.0, max_value=1.0) +def silly(x): + '''Compute a rather uninteresting function + + Parameters + ---------- + x: float + Argument for the function + + Returns + ------- + float: + Value computed by the function + ''' + return x**2 - 2.0 + + +if __name__ == '__main__': + print(silly(0.5)) + try: + print(silly(2.5)) + except ValueError as e: + print(f'Exception raised as expected: {e}') diff --git a/source-code/object-orientation/dynamic_classes.ipynb b/source-code/object-orientation/dynamic_classes.ipynb index 01c7f98..f64dce8 100644 --- a/source-code/object-orientation/dynamic_classes.ipynb +++ b/source-code/object-orientation/dynamic_classes.ipynb @@ -119,7 +119,15 @@ "metadata": {}, "outputs": [], "source": [ - "Cat = make_dataclass('Cat', [('species', str, 'cat'), ('nr_legs', int, 4), ('sound', str, 'meow')], bases=(Animal, ))" + "Cat = make_dataclass(\n", + " 'Cat',\n", + " [\n", + " ('species', str, 'cat'),\n", + " ('nr_legs', int, 4),\n", + " ('sound', str, 'meow')\n", + " ],\n", + " bases=(Animal, )\n", + ")" ] }, { @@ -202,7 +210,15 @@ "metadata": {}, "outputs": [], "source": [ - "Dog = make_dataclass('Dog', [('nr_legs', int, 4), ('species', str, 'dog'), ('sound', str, 'woof')], bases=(Animal, ))" + "Dog = make_dataclass(\n", + " 'Dog',\n", + " [\n", + " ('nr_legs', int, 4),\n", + " ('species', str, 'dog'),\n", + " ('sound', str, 'woof')\n", + " ],\n", + " bases=(Animal, )\n", + ")" ] }, { @@ -423,7 +439,16 @@ "metadata": {}, "outputs": [], "source": [ - "Animal = type('Animal', tuple(), {'name': None, 'species': None, 'nr_legs': None, '__init__': animal_init})" + "Animal = type(\n", + " 'Animal',\n", + " tuple(),\n", + " {\n", + " 'name': None,\n", + " 'species': None,\n", + " 'nr_legs': None,\n", + " '__init__': animal_init,\n", + " }\n", + ")" ] }, { @@ -579,7 +604,16 @@ "metadata": {}, "outputs": [], "source": [ - "Cat = type('Cat', (Animal, ), {'name': None, 'species': 'cat', 'nr_legs': 4, 'sound': 'meow'})" + "Cat = type(\n", + " 'Cat',\n", + " (Animal, ),\n", + " {\n", + " 'name': None,\n", + " 'species': 'cat',\n", + " 'nr_legs': 4,\n", + " 'sound': 'meow',\n", + " }\n", + ")" ] }, { @@ -970,7 +1004,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/source-code/oo_vs_functional.ipynb b/source-code/oo_vs_functional.ipynb index a7a1319..aaaa397 100644 --- a/source-code/oo_vs_functional.ipynb +++ b/source-code/oo_vs_functional.ipynb @@ -488,15 +488,15 @@ "create_coro_stats(stats)\n", " create a coroutine that keeps track of descriptive\n", " statistics of a data stream\n", - " \n", - " Params\n", - " ------\n", + "\n", + " Parameters\n", + " ----------\n", " stats : dict\n", " dictrionary that will hold the statistics, i.e., number of values,\n", " mean and standard deviation. The dictionary will contain None for\n", " the mean and standard deviation if not enough values were send to\n", " the statistics coroutine.\n", - " \n", + "\n", " Example\n", " -------\n", " stats_dict = dict()\n", @@ -985,7 +985,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "304 ms ± 11.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "108 ms ± 2.34 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], @@ -1006,7 +1006,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "516 ms ± 10.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "274 ms ± 8.34 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], @@ -1029,7 +1029,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "373 ms ± 1.39 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "251 ms ± 10.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], @@ -1050,7 +1050,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "465 ms ± 3.52 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "335 ms ± 5.16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], @@ -1071,7 +1071,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "456 ms ± 4.62 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "355 ms ± 9.27 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], @@ -1089,7 +1089,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "315 ms ± 4.28 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "128 ms ± 2.96 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], @@ -1110,7 +1110,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "303 ms ± 12.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "126 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], @@ -1304,16 +1304,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "[32] -> 32\n", - "[32, 37] -> 34.5\n", - "[17, 32, 37] -> 32\n", - "[3, 17, 32, 37] -> 24.5\n", - "[3, 3, 17, 32, 37] -> 17\n", - "[3, 3, 17, 32, 37, 49] -> 24.5\n", - "[3, 3, 17, 28, 32, 37, 49] -> 28\n", - "[3, 3, 17, 26, 28, 32, 37, 49] -> 27.0\n", - "[3, 3, 17, 26, 28, 32, 37, 41, 49] -> 28\n", - "[3, 3, 17, 26, 28, 32, 37, 41, 41, 49] -> 30.0\n" + "[8] -> 8\n", + "[8, 46] -> 27.0\n", + "[1, 8, 46] -> 8\n", + "[1, 8, 15, 46] -> 11.5\n", + "[1, 8, 15, 22, 46] -> 15\n", + "[1, 2, 8, 15, 22, 46] -> 11.5\n", + "[1, 2, 8, 8, 15, 22, 46] -> 8\n", + "[1, 2, 8, 8, 15, 22, 37, 46] -> 11.5\n", + "[1, 2, 3, 8, 8, 15, 22, 37, 46] -> 8\n", + "[1, 2, 3, 7, 8, 8, 15, 22, 37, 46] -> 8.0\n" ] } ], @@ -1509,7 +1509,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -1531,7 +1531,7 @@ { "data": { "text/plain": [ - "(347, 448)" + "(340, 462)" ] }, "execution_count": 38, @@ -1568,7 +1568,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.2" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/source-code/prime_time.ipynb b/source-code/prime_time.ipynb index a2b6776..b257c6c 100644 --- a/source-code/prime_time.ipynb +++ b/source-code/prime_time.ipynb @@ -263,7 +263,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/source-code/pydantic.ipynb b/source-code/pydantic.ipynb index 535e40b..c29590d 100644 --- a/source-code/pydantic.ipynb +++ b/source-code/pydantic.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 1, "id": "b2ba87d0-2447-4a4d-9bae-eb0e86d39ce1", "metadata": {}, "outputs": [], @@ -239,6 +239,136 @@ "As you can see, a Python `dataclass` will not perform validation out-of-the-box." ] }, + { + "cell_type": "markdown", + "id": "f9d4e8b1-22b7-4c25-adc0-fb8eb46ed26f", + "metadata": {}, + "source": [ + "## Default values" + ] + }, + { + "cell_type": "markdown", + "id": "3b808ae6-0b73-4fb7-b570-593243b15448", + "metadata": {}, + "source": [ + "Attributes can have default values. For types such as `int` or `str` these are simple constants for non-trivial types you can use `default_factory`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e00b9f60-5be3-4cea-9dcd-02497911c33e", + "metadata": {}, + "outputs": [], + "source": [ + "class Agent(BaseModel):\n", + " name: str\n", + " interactive: bool = False\n", + " tools: list[str] = Field(default_factory=list)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "98381d5a-c8ae-4c70-958d-43206747766c", + "metadata": {}, + "outputs": [], + "source": [ + "agent1 = Agent(name='my_agent')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c664f054-64fc-4a35-8cd6-77f9429313d4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent1.interactive" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f031bf63-9de5-4c96-b153-e510da32f223", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent1.tools" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "61bf4564-9266-4edd-a55a-3d796daeffa9", + "metadata": {}, + "outputs": [], + "source": [ + "agent1.tools.append('screwdriver')" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "e5b860c1-530b-4cf7-8e79-30806039ce35", + "metadata": {}, + "outputs": [], + "source": [ + "agent2 = Agent(name='my_other_agent')" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "edfcebbe-5a7d-4c1c-b47a-2d8c3ddd54f3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent2.tools" + ] + }, + { + "cell_type": "markdown", + "id": "094eb08a-1ae8-4d42-9c18-d635ea3fca70", + "metadata": {}, + "source": [ + "It is clear that using `default_factory` results in the right behavior." + ] + }, { "cell_type": "markdown", "id": "c31de1d8-2744-48b7-9914-bfeb90cfdac0", @@ -608,7 +738,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.7" + "version": "3.12.3" } }, "nbformat": 4,