summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimeon Simeonov2022-11-14 23:14:31 +0100
committerSimeon Simeonov2022-11-14 23:14:31 +0100
commit013f97e04556c7c0980d673818a77f40fa7882ca (patch)
treec7deb19f8b39ca2526ec0a3ba2eecb6590dd8170
parent72ff13968fc20f043fdec657b4cfe385f4666146 (diff)
Add notebooks/python/python_intro.ipynb
-rw-r--r--notebooks/python/python_intro.ipynb928
1 files changed, 928 insertions, 0 deletions
diff --git a/notebooks/python/python_intro.ipynb b/notebooks/python/python_intro.ipynb
new file mode 100644
index 0000000..a2acfbc
--- /dev/null
+++ b/notebooks/python/python_intro.ipynb
@@ -0,0 +1,928 @@
1{
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "id": "637cdd40-65ae-43e5-8add-d60b89a5846c",
6 "metadata": {},
7 "source": [
8 "# Introduction to Python\n",
9 "\n",
10 "\n",
11 "## Goals\n",
12 "\n",
13 "- Present the Python programming language in a different way than [https://docs.python.org](https://docs.python.org)\n",
14 "\n",
15 "- Avoid information overload\n",
16 "\n",
17 "- Use examples and interaction rather than documents and slides\n",
18 "\n",
19 "\n",
20 "## Preliminary plan\n",
21 "\n",
22 "- **Basics: About the language, the Python eco-system, types, modules, functions, scopes, decorators, string formatting**\n",
23 "\n",
24 "- Object-oriented programming in Python: How Python \"really works\"\n",
25 "\n",
26 "- Control flow: if / for / while / try, iterators, \"tactical programming\" tips\n",
27 "\n",
28 "- A brief tour through Python's standard library\n",
29 "\n",
30 "- Code design and best practices: How to design your code"
31 ]
32 },
33 {
34 "cell_type": "markdown",
35 "id": "5a72ab1d-f313-4f5f-93c7-60c1e07f8c22",
36 "metadata": {},
37 "source": [
38 "## What is Python?\n",
39 "\n",
40 "- Python is an interpreted high-level general-purpose programming language - advanced through the Python Enhancement Proposal (PEP) process\n",
41 "\n",
42 "- CPython is the reference implementation of Python, written in C (alternatives: pypy, jython)\n",
43 "\n",
44 "- python - interpreter and interpreter shell (alternatives: ipython, bpython)\n",
45 "\n",
46 "- libpython\n",
47 "\n",
48 "- Calling C from Python: Cython, CFFI, ctypes"
49 ]
50 },
51 {
52 "cell_type": "markdown",
53 "id": "6d1edcfc-b001-4393-9c24-72adc9b5fccf",
54 "metadata": {},
55 "source": [
56 "## Philosophy\n",
57 "\n",
58 "```python\n",
59 "import this\n",
60 "```"
61 ]
62 },
63 {
64 "cell_type": "code",
65 "execution_count": 96,
66 "id": "54c9e132-30b0-41bf-97e9-0e8b94f3d202",
67 "metadata": {},
68 "outputs": [],
69 "source": [
70 "import this"
71 ]
72 },
73 {
74 "cell_type": "markdown",
75 "id": "4f144f73-51c1-419d-b71e-ee513f68f41c",
76 "metadata": {},
77 "source": [
78 "## Built-in functions\n",
79 "\n",
80 "Few built-in functions.\n",
81 "\n",
82 "[https://docs.python.org/3/library/functions.html](https://docs.python.org/3/library/functions.html)\n",
83 "\n",
84 "- `dir([obj])` - returns a list of valid attributes for that object\n",
85 "\n",
86 "- `id(obj)` - returns the \"identity\" of an object - an integer which is guaranteed to be unique\n",
87 "\n",
88 "- `print(...)` - prints objects to a text stream\n",
89 "\n",
90 "- `str(...)` - returns a string version of object\n",
91 "\n",
92 "- `type(obj)` - returns the type of an object"
93 ]
94 },
95 {
96 "cell_type": "markdown",
97 "id": "a0c57535-e285-47af-80f9-1d4a16de5f25",
98 "metadata": {},
99 "source": [
100 "## Common built-in types\n",
101 "\n",
102 "Python uses duck typing and has typed objects but untyped variable names.\n",
103 "\n",
104 "Type constraints are not checked at compile time; rather, operations on an object may fail, signifying that the given object is not of a suitable type.\n",
105 "Despite being dynamically-typed, Python is strongly-typed, forbidding operations that are not well-defined (for example, adding a number to a string) rather than silently attempting to make sense of them.\n",
106 "\n",
107 "Variables / values must be of a certain type (class)."
108 ]
109 },
110 {
111 "cell_type": "code",
112 "execution_count": 97,
113 "id": "bd6134d9-cc0e-4b45-b6b3-50210f6395e2",
114 "metadata": {},
115 "outputs": [],
116 "source": [
117 "# Common built-in types:\n",
118 "\n",
119 "s = 'foo' # this is a string / str, same as str('foo'), may be encoded, immutable (s[0] = 'r' is NOT possible)\n",
120 "\n",
121 "b = b'foo' # bytes, same as bytes('foo', 'utf-8'), may be decoded, immutable\n",
122 "\n",
123 "i = 6 # int, same as int('6'), immutable\n",
124 "\n",
125 "f = 0.1 # float, same as float('0.1'), immutable\n",
126 "\n",
127 "b = False # bool, same as bool(0), bool(''), bool(None)... immutable / constant\n",
128 "\n",
129 "n = None # NoneType, similar to 'null' in other languages, immutable / constant\n",
130 "\n",
131 "l = [1, False, 'foo'] # list, same as list((1, False, 'foo'))\n",
132 "\n",
133 "t = (1, False, 'foo') # tuple, same as tuple([1, False, 'foo']), immutable\n",
134 "\n",
135 "d = {'foo': 1, 'bar': 8} # dict, same as dict(foo=1, bar=8), similar to hash in other languages\n",
136 "\n",
137 "s = {'foo', 'bar', 1, 1, 4} # set, same as set(['foo', 'bar', 1, 1, 4]), removes duplicates"
138 ]
139 },
140 {
141 "cell_type": "markdown",
142 "id": "6b2bc8fa-a5dc-4a58-92b2-abfb19bfe2ed",
143 "metadata": {},
144 "source": [
145 "## Modules\n",
146 "\n",
147 "A module is a file containing Python definitions and statements. The file name is the module name with the suffix *.py* appended. Within a module, the module's name (as a string) is available as the value of the global variable `__name__`\n",
148 "\n",
149 "When a module named *\"foo\"* is imported, the interpreter first searches for a built-in module with that name (`sys.builtin_module_names`). If not found, it then searches for a file named *foo.py* in a list of directories given by the variable `sys.path`. `sys.path` is initialized from these locations:\n",
150 "\n",
151 "- the directory containing the input script (or the current directory when no file is specified)\n",
152 "\n",
153 "- *PYTHONPATH* - env. variable - a list of directory names\n",
154 "\n",
155 "- the installation-dependent default locations\n",
156 "\n",
157 "The module is then imported only once and \"cached\" in `sys.modules`\n"
158 ]
159 },
160 {
161 "cell_type": "markdown",
162 "id": "6c55e90b-9385-4ebd-90e9-e29654096287",
163 "metadata": {},
164 "source": [
165 "## Packages\n",
166 "\n",
167 "Packages are a way of structuring Python's module namespace by using \"dotted module names\"\n",
168 "\n",
169 "The import statement combines two operations:\n",
170 "\n",
171 "- it searches for the named module\n",
172 "\n",
173 "- it binds the results of that search to a name in the *local scope*\n",
174 "\n",
175 "\n",
176 "```python\n",
177 "# bar/__init__.py then bar.py will be considered, the first match executed and bound to 'bar'\n",
178 "import bar\n",
179 "\n",
180 "import mymodule.foo # implicitly executes mymodule/__init__.py (or mymodule.py) and mymodule/foo/__init__.py\n",
181 "\n",
182 "import numpy as np # will be bound as 'np' instead of 'numpy'. N.B. __name__ is still 'numpy'\n",
183 "\n",
184 "import some.extremely.deep.path.Animal as Animal # \"sacrifice\" the namespace in the name of convinience\n",
185 "\n",
186 "from sys import path # execute sys and only import the 'path' attribute into local scope as 'path'\n",
187 "\n",
188 "# relative imports must be explicit in Python 3\n",
189 "from .othermodule import something # expects that current module and 'othermodule' are in the same\n",
190 " # package (containing __init__.py)\n",
191 "\n",
192 "from sys import * # NO! Bad programming practice since 1879\n",
193 "```"
194 ]
195 },
196 {
197 "cell_type": "markdown",
198 "id": "b26c4bd1-58ea-44bc-aedd-e6b6405da015",
199 "metadata": {},
200 "source": [
201 "## Creating and maintaining a Python environment\n",
202 "\n",
203 "Python's official package repository is PyPi [https://pypi.org](https://pypi.org), while Python's official package installer is *pip* [https://pypi.org/project/pip/](https://pypi.org/project/pip/)\n",
204 "\n",
205 "A Python environment is the physical and logical arrangement of Python modules and packages. Several options exist:\n",
206 "\n",
207 "- using a proper operating system :) (symlinks, real commercial support etc.)\n",
208 "\n",
209 "- using venv\n",
210 "\n",
211 "- using higher level tools like *poetry*\n",
212 "\n",
213 "- using a mixture / cocktail of all of the above :)"
214 ]
215 },
216 {
217 "cell_type": "markdown",
218 "id": "40db50e4-fb7a-4a77-a074-bc4413f27212",
219 "metadata": {},
220 "source": [
221 "## Creating and maintaining a Python environment (cont...)\n",
222 "\n",
223 "Desired qualities for a flexible Python environment:\n",
224 "\n",
225 "- easy to create and (un)load\n",
226 "\n",
227 "- do not require extra privileges\n",
228 "\n",
229 "- don't repeat yourself (DRY)\n",
230 "\n",
231 "- easy to update without breaking the API\n",
232 "\n",
233 "- easy to debug\n",
234 "\n",
235 "- play nicely with the VCS (git)"
236 ]
237 },
238 {
239 "cell_type": "markdown",
240 "id": "c661bf56-add0-402c-a629-a857f81d0758",
241 "metadata": {},
242 "source": [
243 "## Creating and maintaining a Python environment (cont...)\n",
244 "\n",
245 "Exploting the operating system can be done by:\n",
246 "\n",
247 "- (re)defining `PYTHONPATH`\n",
248 "- using symlinks to point at packages placed at different locations"
249 ]
250 },
251 {
252 "cell_type": "markdown",
253 "id": "db8952aa-0944-4644-9626-7460786fd72a",
254 "metadata": {},
255 "source": [
256 "## Creating and maintaining a Python environment (cont...)\n",
257 "\n",
258 "Using venv can be done by directly invoking python:\n",
259 "\n",
260 "```bash\n",
261 "# create a virtual environment\n",
262 "python -m venv my_virtual_env\n",
263 "python -m venv --system-site-packages my_virtual_env\n",
264 "\n",
265 "# load, use and unload the virtual environment\n",
266 "source my_virtual_env/bin/activate\n",
267 "pip install sqlalchemy\n",
268 "# install package from a custom repository (https://artifactory.fifty.eu)\n",
269 "pip install --index-url=https://artifactory.fifty.eu/artifactory/api/pypi/pypi/simple/ odin-data-access\n",
270 "deactivate\n",
271 "\n",
272 "# one can alternatively use the python \"wrapper\" of the virtual env\n",
273 "my_virtual_env/bin/python -m pip install sqlalchemy\n",
274 "```\n",
275 "\n",
276 "*--system-site-packages* will keep the original *site-packages* folders at the end of `sys.path`"
277 ]
278 },
279 {
280 "cell_type": "markdown",
281 "id": "299641e6-3d87-4a86-a124-eb49edb75b4b",
282 "metadata": {},
283 "source": [
284 "## Creating and maintaining a Python environment (cont...)\n",
285 "\n",
286 "Poetry [https://python-poetry.org](https://python-poetry.org) is the prefered environment and dependency management tool at Statnett.\n",
287 "\n",
288 "```bash\n",
289 "# create project and a virtual environment from scratch\n",
290 "poetry new my-project\n",
291 "\n",
292 "# ... or use Poetry with an existing one\n",
293 "cd my-project\n",
294 "poetry init\n",
295 "\n",
296 "# edit pyproject.toml for your needs (f.i. add dependencies, metadata ... etc),\n",
297 "# create virtual environment and install dependencies\n",
298 "poetry install\n",
299 "# it will create the file poetry.lock\n",
300 "# finally commit your poetry.lock file to version control\n",
301 "\n",
302 "# update all dependencies and poetry.lock\n",
303 "poetry update\n",
304 "```\n",
305 "\n",
306 "For more info: [https://python-poetry.org/docs/basic-usage/](https://python-poetry.org/docs/basic-usage/)"
307 ]
308 },
309 {
310 "cell_type": "markdown",
311 "id": "a60540b1-ad17-4c40-88e1-e6d6af1ab219",
312 "metadata": {},
313 "source": [
314 "## Mutables vs. immutables\n",
315 "\n",
316 "Immutable object is an object with a fixed value. Immutable objects include `bool`, `int`, `float`, `str`, `bytes` and `tuple`. Such an object cannot be altered. A new object has to be created if a different value has to be stored. They play an important role in places where a constant hash value is needed, for example as a key in a dictionary.\n",
317 "\n",
318 "All objects that are not immutable are... mutable. All *hashable objects* **should** be immutable or use `id()`."
319 ]
320 },
321 {
322 "cell_type": "code",
323 "execution_count": 98,
324 "id": "f4a4ea99-4038-4b61-9b8d-9ce623e6a663",
325 "metadata": {},
326 "outputs": [
327 {
328 "data": {
329 "text/plain": [
330 "35041378544"
331 ]
332 },
333 "execution_count": 98,
334 "metadata": {},
335 "output_type": "execute_result"
336 },
337 {
338 "data": {
339 "text/plain": [
340 "35041378576"
341 ]
342 },
343 "execution_count": 98,
344 "metadata": {},
345 "output_type": "execute_result"
346 },
347 {
348 "data": {
349 "text/plain": [
350 "'H'"
351 ]
352 },
353 "execution_count": 98,
354 "metadata": {},
355 "output_type": "execute_result"
356 },
357 {
358 "data": {
359 "text/plain": [
360 "-6333845781340707986"
361 ]
362 },
363 "execution_count": 98,
364 "metadata": {},
365 "output_type": "execute_result"
366 },
367 {
368 "data": {
369 "text/plain": [
370 "36321214208"
371 ]
372 },
373 "execution_count": 98,
374 "metadata": {},
375 "output_type": "execute_result"
376 },
377 {
378 "data": {
379 "text/plain": [
380 "36321613808"
381 ]
382 },
383 "execution_count": 98,
384 "metadata": {},
385 "output_type": "execute_result"
386 },
387 {
388 "data": {
389 "text/plain": [
390 "27542959409390741"
391 ]
392 },
393 "execution_count": 98,
394 "metadata": {},
395 "output_type": "execute_result"
396 },
397 {
398 "data": {
399 "text/plain": [
400 "27542959409390741"
401 ]
402 },
403 "execution_count": 98,
404 "metadata": {},
405 "output_type": "execute_result"
406 }
407 ],
408 "source": [
409 "i = 1\n",
410 "id(i) # returns f.i. 9788992\n",
411 "i += 1 # same as i = i + 1\n",
412 "id(i) # returns a different value, hence - a brand new object\n",
413 "\n",
414 "s = 'Hello'\n",
415 "s += ' World' # s is now a different object\n",
416 "s[0] # 'H'\n",
417 "# s[0] = 'h' # TypeError: 'str' object does not support item assignment\n",
418 "\n",
419 "t = (1, 4) # tuple\n",
420 "l = [1, 4] # list\n",
421 "hash(t) # returns f.i. -6333845781340707986\n",
422 "# hash(l) # TypeError: unhashable type: 'list'\n",
423 "\n",
424 "s = 'the long and winding road'\n",
425 "s2 = 'the long and winding road'\n",
426 "\n",
427 "# check if s and s2 are the same object:\n",
428 "id(s) # Out: 139858905258704\n",
429 "id(s2) # Out: 139858926037472\n",
430 "\n",
431 "# the hash should be the same\n",
432 "hash(s) # Out: 7030216208569256362\n",
433 "hash(s2) # Out: 7030216208569256362"
434 ]
435 },
436 {
437 "cell_type": "markdown",
438 "id": "ba19a7d1-866c-40dc-ba9e-a04c8af5f1ba",
439 "metadata": {},
440 "source": [
441 "## Functions\n",
442 "\n",
443 "A function is a sequence of program instructions that performs a specific task, packaged as a unit.\n",
444 "\n",
445 "Functions let you:\n",
446 "\n",
447 "- reuse code across several programs / projects\n",
448 "\n",
449 "- minimize code duplication\n",
450 "\n",
451 "- devide larger programming tasks\n",
452 "\n",
453 "- hide implementation details\n",
454 "\n",
455 "- improve readability\n",
456 "\n",
457 "- improve traceability\n",
458 "\n",
459 "Function calls bring some overhead pushing / popping function-data into / from stack.\n",
460 "\n",
461 "Important definitions (may have different meanings in different programming languages):\n",
462 "\n",
463 "- *parameter / formal parameter* - variable / data provided as input to the function\n",
464 "\n",
465 "- *argument / actual parameter* - local variable / data to the given function\n",
466 "\n",
467 "The keyword `def` introduces a function definition."
468 ]
469 },
470 {
471 "cell_type": "code",
472 "execution_count": 99,
473 "id": "bf95f44a-1257-440f-b35f-a8536c8c4363",
474 "metadata": {},
475 "outputs": [
476 {
477 "data": {
478 "text/plain": [
479 "7"
480 ]
481 },
482 "execution_count": 99,
483 "metadata": {},
484 "output_type": "execute_result"
485 },
486 {
487 "name": "stdout",
488 "output_type": "stream",
489 "text": [
490 "9\n"
491 ]
492 },
493 {
494 "data": {
495 "text/plain": [
496 "[9, -2]"
497 ]
498 },
499 "execution_count": 99,
500 "metadata": {},
501 "output_type": "execute_result"
502 },
503 {
504 "name": "stdout",
505 "output_type": "stream",
506 "text": [
507 "[9, 5]\n"
508 ]
509 }
510 ],
511 "source": [
512 "# Functions (example1)\n",
513 "\n",
514 "def add(a, b): # - function definition / header\n",
515 " \"\"\"Function for adding integers\"\"\" # - docstring\n",
516 " result = a + b\n",
517 " a = 5 # will not change the corresponding parameter since it is immutable\n",
518 " return result # - function that does not contain return, implicitly returns None\n",
519 "\n",
520 "a_param = 9\n",
521 "b_param = -2\n",
522 "add(a_param, b_param) # Out: 7\n",
523 "\n",
524 "# integers are immutable and a_param will remain unchanged\n",
525 "print(a_param) # Out: 9\n",
526 "\n",
527 "\n",
528 "def addl(a, b):\n",
529 " \"\"\"Function for adding two lists\"\"\"\n",
530 " result = a + b\n",
531 " a += [5] # in this case equal to: a.append(5)\n",
532 " return result\n",
533 "\n",
534 "a_param = [9]\n",
535 "b_param = [-2]\n",
536 "addl(a_param, b_param) # Out: [9, -2]\n",
537 "# lists are mutable and a_param will be changed\n",
538 "print(a_param) # Out: [9, 5]"
539 ]
540 },
541 {
542 "cell_type": "code",
543 "execution_count": 100,
544 "id": "ecd03ee2-3523-40bd-afd1-4263a777bae0",
545 "metadata": {},
546 "outputs": [],
547 "source": [
548 "# Functions (example2) - parameters and arguments\n",
549 "\n",
550 "def add(a, b):\n",
551 " \"\"\"Function for adding integers\"\"\"\n",
552 " return a + b\n",
553 "\n",
554 "my_result = add(2, 5) # positional arguments (parameters)\n",
555 "my_result = add(b=5, a=2) # keyword arguments (parameters)\n",
556 "\n",
557 "my_tuple = (2, 5)\n",
558 "my_dict = {'b': 5, 'a': 2}\n",
559 "\n",
560 "my_result = add(*my_tuple) # unpacked and assigned to the positional arguments\n",
561 "my_result = add(**my_dict) # unpacked and assigned to the kw. arguments\n",
562 "\n",
563 "def add(a, b=5):\n",
564 " \"\"\"Function for adding integers\"\"\"\n",
565 " return a + b\n",
566 "my_result = add(2)\n",
567 "# ... and the rest of the examples above will work\n",
568 "\n",
569 "def add(a, *args, **kwargs):\n",
570 " \"\"\"Function for adding integers\"\"\"\n",
571 " if args:\n",
572 " b = args[0]\n",
573 " elif 'b' in kwargs:\n",
574 " b = kwargs['b']\n",
575 " return a + b\n",
576 "\n",
577 "my_result = add(2, 5, 9, 11) # 5 assigned to args[0]\n",
578 "my_result = add(2, b=5, c=9, d=11) # 5 assigned to kwargs['b']\n",
579 "\n",
580 "# my_result = add(2, b=5, 9, 11) # SyntaxError: positional argument follows keyword argument"
581 ]
582 },
583 {
584 "cell_type": "markdown",
585 "id": "26a4f2fe-b79e-4e25-9107-230a53d11373",
586 "metadata": {},
587 "source": [
588 "## Functions (cont ...)\n",
589 "\n",
590 "Docstrings annotations and other hints\n",
591 "\n",
592 "```python\n",
593 "def decrypt(password: str, edata: str) -> str:\n",
594 " \"\"\"\n",
595 " Decrypts `edata` using `password`.\n",
596 "\n",
597 " `edata` is in the following format:\n",
598 " enc-val$`version-num`$`bas64-salt`$`base64-encrypted_data`\n",
599 "\n",
600 " :param password: The password to generate the key with\n",
601 " :type password: str\n",
602 "\n",
603 " :param edata: The data to be decrypted\n",
604 " :type edata: str\n",
605 "\n",
606 " :raises EtoolkitInstanceError: If the encryption format is unsupported\n",
607 "\n",
608 " :return: The output string / decrypted data\n",
609 " :rtype: str\n",
610 " \"\"\"\n",
611 " if not edata.startswith('enc-val$1$'):\n",
612 " raise EtoolkitInstanceError('Unsupported encryption format')\n",
613 " # some more code magic coming after....\n",
614 " # ...\n",
615 " # ..\n",
616 " return decrypted_str\n",
617 "```\n",
618 "\n",
619 "Using `typing` for more advanced / flexible hinting\n",
620 "\n",
621 "```python\n",
622 " \n",
623 "import typing\n",
624 "\n",
625 "Basestring = typing.Union[str, bytes]\n",
626 "\n",
627 "def decrypt(password: Basestring, edata: str) -> str:\n",
628 " pass\n",
629 "\n",
630 "\n",
631 "# or simply...\n",
632 "from typing import Union\n",
633 "\n",
634 "def decrypt(password: Union[str, bytes], edata: str) -> str:\n",
635 " \"\"\"Generic documentation. No need for pass\"\"\"\n",
636 "\n",
637 "# Python >= 3.10 only\n",
638 "def decrypt(password: str | bytes, edata: str) -> str:\n",
639 " \"\"\"Generic documentation. No need for pass\"\"\"\n",
640 "```"
641 ]
642 },
643 {
644 "cell_type": "markdown",
645 "id": "40b749b3-5ec6-4c81-a9ee-3327433b4997",
646 "metadata": {},
647 "source": [
648 "## Functions (cont ...)\n",
649 "\n",
650 "Functions as parameters / arguments, lambdas and returning multiple values\n",
651 "\n",
652 "Functions in Python are callable objects. Callable objects can be created by defining the `__call__` method. More on that later in the course..."
653 ]
654 },
655 {
656 "cell_type": "code",
657 "execution_count": 101,
658 "id": "edf9cfa7-d76c-428b-a39f-8c0697789031",
659 "metadata": {},
660 "outputs": [],
661 "source": [
662 "def fetch_the_first_letter(input_str: str) -> str:\n",
663 " \"\"\"Fetches the first letter of the string input_str or 'x'\"\"\"\n",
664 " try:\n",
665 " return input_str[0]\n",
666 " except Exception:\n",
667 " return 'x'\n",
668 "letter_list = list(map(fetch_the_first_letter, ['foo', 'bar', 'test'])) # Out: ['f', 'b', 't']\n",
669 "\n",
670 "\n",
671 "# Small anonymous functions can be created with the lambda keyword\n",
672 "letter_list = list(map(lambda x: x[0], ['foo', 'bar', 'test'])) # Out: ['f', 'b', 't']\n",
673 "\n",
674 "\n",
675 "# A function can return multiple values by implicitly returning a tuple:\n",
676 "\n",
677 "def square_cube(x):\n",
678 " \"\"\"returns x, x^2 and x^3\"\"\"\n",
679 " return x, x**2, x**3\n",
680 "\n",
681 "numbers = square_cube(5) # Out: (5, 25, 125)\n",
682 "num, sqnum, cbnum = square_cube(5) # unpacking the tuple"
683 ]
684 },
685 {
686 "cell_type": "markdown",
687 "id": "d890df54-4097-43b5-a96d-1d9996e5626f",
688 "metadata": {},
689 "source": [
690 "## Functions (cont ...)\n",
691 "\n",
692 "Enclosing and nested functions\n",
693 "\n",
694 "Can be used as:\n",
695 "\n",
696 "- regular functions within functions\n",
697 "\n",
698 "- dynamic function factories"
699 ]
700 },
701 {
702 "cell_type": "code",
703 "execution_count": 102,
704 "id": "ea583c17-c938-495a-9586-2027c66328a2",
705 "metadata": {},
706 "outputs": [
707 {
708 "name": "stdout",
709 "output_type": "stream",
710 "text": [
711 "9\n",
712 "15\n"
713 ]
714 }
715 ],
716 "source": [
717 "# enclosing functions\n",
718 "\n",
719 "def get_multiplier_of(base: int) -> str:\n",
720 " \"\"\"the function enclosing its nested functions\"\"\"\n",
721 "\n",
722 " def multiplier_function(x):\n",
723 " \"\"\"a nested function\"\"\"\n",
724 " return base * x\n",
725 "\n",
726 " return multiplier_function\n",
727 "\n",
728 "times3 = get_multiplier_of(3)\n",
729 "times5 = get_multiplier_of(5)\n",
730 "print(times3(3)) # Out: 9\n",
731 "print(times5(3)) # Out: 15"
732 ]
733 },
734 {
735 "cell_type": "markdown",
736 "id": "cb5d1ad1-b702-41c3-92c4-93ed3df4e0c0",
737 "metadata": {},
738 "source": [
739 "## Scopes in Python\n",
740 "\n",
741 "- local - assigned names are local unless declared global\n",
742 "\n",
743 "- enclosed - the scope of the variable inside a function with a nested function\n",
744 "\n",
745 "- global - global for the current module\n",
746 "\n",
747 "- built-in\n",
748 "\n",
749 "`locals()` and `globals()` return dicts of symbols for their respective scopes"
750 ]
751 },
752 {
753 "cell_type": "code",
754 "execution_count": 103,
755 "id": "49d7f6e1-1f54-4b96-8203-5eae95f47ebe",
756 "metadata": {},
757 "outputs": [
758 {
759 "name": "stdout",
760 "output_type": "stream",
761 "text": [
762 "7 8\n",
763 "100\n",
764 "{'num3': 100, 'id': 200}\n",
765 "{}\n",
766 "{}\n",
767 "99\n"
768 ]
769 }
770 ],
771 "source": [
772 "num1, num2 = 7, 8 # module globals\n",
773 "\n",
774 "def print_numbers():\n",
775 " print(num1, num2) # OK, these are module globals\n",
776 " num3 = 100\n",
777 " print(num3) # prints 100, num3 is in the function (local) scope\n",
778 " global num4 # assignes / references num4 to / in the global scope\n",
779 " num4 = 99\n",
780 " id = 200 # new symbol in local scope\n",
781 " # id(num4) # will not yield the expected result (raises TypeError)\n",
782 " print(locals())\n",
783 "\n",
784 " def print_numbers2():\n",
785 " print(locals())\n",
786 " # print(num3) # OK, enclosed scope\n",
787 " print(locals())\n",
788 "\n",
789 " print_numbers2() # prints 100\n",
790 "\n",
791 "print_numbers()\n",
792 "# print(num3) # Raises NameError - why?\n",
793 "print(num4) # Prints 99 - why?\n",
794 "# print_numbers2() # Raises NameError"
795 ]
796 },
797 {
798 "cell_type": "markdown",
799 "id": "5c91e152-a658-4811-832f-90712d708f8d",
800 "metadata": {},
801 "source": [
802 "## Decorators\n",
803 "\n",
804 "Decorators can be used to modify the behavior of the objects they decorate. Decorators can be implemented either by using classes or by using nested functions.\n",
805 "\n",
806 "```python \n",
807 "def my_decorator(func):\n",
808 "\n",
809 " def decorated():\n",
810 " print('Doing something before the decorated function')\n",
811 " retval = func()\n",
812 " print('Doing something after the decorated function')\n",
813 " return retval\n",
814 " return decorated\n",
815 "\n",
816 "def my_function():\n",
817 " print('Alice')\n",
818 "\n",
819 "my_function = my_decorator(my_function)\n",
820 "my_function()\n",
821 "``` \n",
822 "\n",
823 "... may be dificult to read / understand, while:\n",
824 "\n",
825 "```python \n",
826 "@my_decorator\n",
827 "def my_function():\n",
828 " print('Alice')\n",
829 "\n",
830 "my_function()\n",
831 "``` \n",
832 "\n",
833 "... may be easier"
834 ]
835 },
836 {
837 "cell_type": "code",
838 "execution_count": 104,
839 "id": "4346260d-76af-437e-9c10-1c1d0c478695",
840 "metadata": {},
841 "outputs": [],
842 "source": [
843 "# Decorators (cont ...) - a complete example\n",
844 "\n",
845 "import sys\n",
846 "from functools import wraps\n",
847 "\n",
848 "def requires_access(access_secret: str):\n",
849 "\n",
850 " def api_access_decorator(f):\n",
851 "\n",
852 " @wraps(f)\n",
853 " def decorated(*args, **kwargs):\n",
854 " if 'secret' not in kwargs:\n",
855 " sys.exit('No secret provided')\n",
856 " if not kwargs['secret'] or kwargs['secret'] != access_secret:\n",
857 " sys.exit(\"Secret doesn't match\")\n",
858 " # return f(args[0], **kwargs)\n",
859 " return f(*args, **kwargs)\n",
860 "\n",
861 " return decorated\n",
862 "\n",
863 " return api_access_decorator\n",
864 "\n",
865 "\n",
866 "@requires_access(access_secret='b28cfeaa65b73cf')\n",
867 "def sensitive_function(data, **kwargs):\n",
868 " \"\"\"very sensitive function\"\"\"\n",
869 " db.save(data)"
870 ]
871 },
872 {
873 "cell_type": "markdown",
874 "id": "14ea817a-0748-4941-8899-0b9025f25a93",
875 "metadata": {},
876 "source": [
877 "## String formatting\n",
878 "\n",
879 "The old ways...\n",
880 "\n",
881 "```python \n",
882 "f = 6.57865\n",
883 "i = 27\n",
884 "s = 'another string'\n",
885 "\n",
886 "\n",
887 "'%s - %d - %5.2f' % (s, i, f) # Out: 'another string - 27 - 6.58'\n",
888 "\n",
889 "# still used in:\n",
890 "logger.debug(\"%d - %s\", event.id, message)\n",
891 "\n",
892 "# using the .format method\n",
893 "'{} - {} - {:5.2f}'.format(s, i, f) # implicit\n",
894 "'{0} - {1} - {2:5.2f}'.format(s, i, f) # explicit\n",
895 "'{my_str} - {i} - {fl:5.2f}'.format(my_str=s, fl=f, i=i) # keyword\n",
896 "# Out: 'another string - 27 - 6.58'\n",
897 "\n",
898 "\n",
899 "# modern Python >= 3.6 f-strings\n",
900 "f'{s} - {i} - {f:5.2f}' # Out: 'another string - 27 - 6.58'\n",
901 "``` \n",
902 "\n",
903 "See https://docs.python.org/3/library/string.html#formatspec for the complete format specification"
904 ]
905 }
906 ],
907 "metadata": {
908 "kernelspec": {
909 "display_name": "Python 3 (ipykernel)",
910 "language": "python",
911 "name": "python3"
912 },
913 "language_info": {
914 "codemirror_mode": {
915 "name": "ipython",
916 "version": 3
917 },
918 "file_extension": ".py",
919 "mimetype": "text/x-python",
920 "name": "python",
921 "nbconvert_exporter": "python",
922 "pygments_lexer": "ipython3",
923 "version": "3.10.8"
924 }
925 },
926 "nbformat": 4,
927 "nbformat_minor": 5
928}