14.6. Function Recap

  • Automate repetitive tasks

  • Allow code reuse

  • Improve code readability

  • Clean-up code

  • Allow easier refactoring

14.6.1. Define and Call

  • Use snake_case

  • Do not use camelCase

  • Do not use PascalCase

  • Use pass keyword to define empty function

>>> def run():
...     pass
>>>
>>> run()

14.6.2. Return and Capture Results

  • There could be multiple return keywords in a function

  • Code after first return will not execute

  • Function in Python always returns a value

  • Function will return None if no explicit return is specified

>>> def run():
...     return 'something'
...
>>> x = run()
>>> print(x)
something

14.6.3. Parameters

  • Parameter - what you specify while defining a function

  • Parameters could be required or default

  • Required parameter - necessary to call that function, specified at leftmost side

  • Default parameter - optional to call that function, has default value, can be overridden, specified at rightmost side

Required parameters:

>>> def run(a, b):
...     pass

Default parameters:

>>> def run(a=1, b=2):
...     pass

Required and default parameters:

>>> def run(a, b=0):
...     pass

14.6.4. Arguments

  • Argument - what you specify while calling a function

  • Arguments could be positional or keyword

  • Positional argument - value passed to function resolved by position - order is important

  • Keyword argument - value passed to function resolved by name - order is not important

Positional arguments:

>>> run(1, 2)

Keyword arguments:

>>> run(a=1, b=2)

Positional and keyword arguments:

>>> run(1, b=2)

14.6.5. Scope

  • Function can have local variables

  • Local variables are destroyed when function returns

  • Functions can access variables in global scope

  • Global variables are not destroyed when function returns

  • Shadowing is when you define local variable with the same name as variable global scope

  • Function will prefer local variable over global

  • After function return, the original value of a shadowed variable is restored

  • Function can modify global variables using global keyword (this is a bad practice)

  • Builtin function globals() returns dictionary of global variables

  • Builtin function locals() returns dictionary of local variables

  • In global scope locals() and globals() are the same

  • In function scope locals() and globals() are different

Accessing local variable:

>>> def run():
...     x = 1
...     print(x)  # function will access local variable

Accessing global variable:

>>> x = 1
>>>
>>> def run():
...     print(x)  # function will access global variable

Shadowing:

>>> x = 1  # global variable
>>>
>>> def run():
...     x = 10  # local variable with the same name as global
...     print(x)  # function will prefer local variable

Return deletes local variables (restores shadowed global variables):

>>> x = 1
>>>
>>> def run():
...     x = 10
...     print(x)
>>> x
1
>>> run()
10
>>> x
1

Modify global variable (considered as a bad practice):

>>> x = 1
>>>
>>> def run():
...     global x
...     x = 10
...     print(x)

Locals vs. Globals:

>>> print(locals() == globals())
True
>>> def run():
...     print(locals() == globals())
...
>>> run()
False

14.6.6. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Function Usecase Min
# - Difficulty: easy
# - Lines: 6
# - Minutes: 5

# %% English
# 1. Define function `mymin`:
#    - takes `data: list[int|float]`
#    - returns lowest value in `data`
# 2. Do not use built-in function `min()`
# 3. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj funkcję `mymin`:
#    - przyjmuje `data: list[int|float]`
#    - zwraca tuplę z najmniejszą wartością z `data`
# 2. Nie używaj wbudowanej funkcji `min()`
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert mymin is not Ellipsis, \
'Write solution inside `mymin` function'
>>> assert isfunction(mymin), \
'Object `mymin` must be a function'

>>> mymin([3,1,2])
1

>>> mymin([3,1,2,6,4,5])
1
"""

# Define function `mymin`:
# - takes `data: list[int|float]`
# - returns tuple with min and max value from `data`
# Do not use built-in `min()` and `max()` functions
# type: Callable[[list[int|float]], int]
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Function Usecase Max
# - Difficulty: easy
# - Lines: 6
# - Minutes: 5

# %% English
# 1. Define function `mymax`:
#    - takes `data: list[int|float]`
#    - returns the greatest value in `data`
# 2. Do not use built-in function `max()`
# 3. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj funkcję `mymax`:
#    - przyjmuje `data: list[int|float]`
#    - zwraca tuplę z największą wartością z `data`
# 2. Nie używaj wbudowanej funkcji `max()`
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert mymax is not Ellipsis, \
'Write solution inside `mymax` function'
>>> assert isfunction(mymax), \
'Object `mymax` must be a function'

>>> mymax([3,1,2])
3

>>> mymax([3,1,2,6,4,5])
6
"""

# Define function `mymax`:
# - takes `data: list[int|float]`
# - returns tuple with min and max value from `data`
# Do not use built-in `min()` and `max()` functions
# type: Callable[[list[int|float]], int]
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Function Usecase Len
# - Difficulty: easy
# - Lines: 5
# - Minutes: 5

# %% English
# 1. Define function `mylen`:
#    - takes `data: list`
#    - returns number of elements in `data`
# 2. Do not use built-in `len()` function
# 3. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj funkcję `mylen`:
#    - przyjmuje `data: list`
#    - zwraca liczbe elementów w `data`
# 2. Nie używaj wbudowanej funkcji `len`
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert mylen is not Ellipsis, \
'Write solution inside `mylen` function'
>>> assert isfunction(mylen), \
'Object `mylen` must be a function'

>>> mylen([1,2,3])
3

>>> mylen([1,2,3,4,5,6])
6
"""

# Define function `mylen`:
# - takes `data: list`
# - returns number of elements in `data`
# Do not use built-in `len()` function
# type: Callable[[list], int]
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Function Arguments Range
# - Difficulty: easy
# - Lines: 7
# - Minutes: 5

# %% English
# 1. Define function `myrange` with parameters: `start`, `stop`, `step`
# 2. Write own implementation of a built-in function
#    `myrange(start, stop, step)`
# 3. Do not use built-in `range()` function
# 4. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj funkcję `myrange` z parametrami: `start`, `stop`, `step`
# 2. Zaimplementuj własne rozwiązanie wbudowanej funkcji
#    `myrange(start, stop, step)`
# 3. Nie używaj wbudowanej funkcji `range()`
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Hints
# - `while`

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert myrange is not Ellipsis, \
'Write solution inside `myrange` function'
>>> assert isfunction(myrange), \
'Object `myrange` must be a function'

>>> myrange(0, 10, 2)
[0, 2, 4, 6, 8]

>>> myrange(0, 5)
[0, 1, 2, 3, 4]
"""

# Define function `myrange` with parameters: `start`, `stop`, `step`
# Write own implementation of a built-in function
# `myrange(start, stop, step)`
# Do not use built-in `range()` function
# type: Callable[[int, int, int], list[int]]
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Function Usecase Enumerate
# - Difficulty: easy
# - Lines: 7
# - Minutes: 5

# %% English
# 1. Define function `myenumerate`:
#    - takes `data: list`
#    - returns tuple list with consecutive number and value from `data`
# 2. Example:
#    - input: ['red', 'green', 'blue']
#    - output: [(0, 'red'), (1, 'green'), (2, 'blue')]
# 3. Do not use built-in `enumerate()` function
# 4. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj funkcję `myenumerate`:
#    - przyjmuje `data: list`
#    - zwraca listę tupli z kolejnym numerem i wartością z `data`
# 2. Przykład:
#    - input: ['red', 'green', 'blue']
#    - output: [(0, 'red'), (1, 'green'), (2, 'blue')]
# 3. Nie używaj wbudowanej funkcji `enumerate`
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert myenumerate is not Ellipsis, \
'Write solution inside `myenumerate` function'
>>> assert isfunction(myenumerate), \
'Object `myenumerate` must be a function'

>>> myenumerate(['red', 'green', 'blue'])
[(0, 'red'), (1, 'green'), (2, 'blue')]
"""

# Define function `myenumerate`:
# - takes `data: list`
# - returns number of elements in `data`
# Do not use built-in `len()` function
# type: Callable[[list], int]
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Function Usecase Zip
# - Difficulty: easy
# - Lines: 8
# - Minutes: 5

# %% English
# 1. Define function `myzip`:
#    - takes two parameters `a: list`, `b: list`
#    - pairs `a` elements with `b` elements in form of `list[tuple]`
#    - first from `a` is paired with first from `b`, etc.
#    - collect number of pairs equal to the count of shortest list
# 2. Example:
#    - input: ['Mark', 'Melissa'] and ['Watney', 'Lewis']
#    - output: [('Mark', 'Watney'), ('Melissa', 'Lewis')]
# 3. Do not use built-in `zip()` function
# 4. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj funkcję `myzip`:
#    - przyjmuje `data: list`
#    - paruje elementy `a` z elementami z `b` w formie `list[tuple]`
#    - pierwszy z `a` ma być sparowany z pierwszym z `b`, itd.
#    - zbierz tyle par ile bło wartości w najkrótszej liście
# 2. Przykład:
#    - input: ['Mark', 'Melissa'] i ['Watney', 'Lewis']
#    - output: [('Mark', 'Watney'), ('Melissa', 'Lewis')]
# 3. Nie używaj wbudowanej funkcji `zip`
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert myzip is not Ellipsis, \
'Write solution inside `myzip` function'
>>> assert isfunction(myzip), \
'Object `myzip` must be a function'

>>> firstnames = ['Mark', 'Melissa', 'Rick']
>>> lastnames = ['Watney', 'Lewis', 'Martinez']
>>> myzip(firstnames, lastnames)
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Rick', 'Martinez')]

>>> firstnames = ['Mark', 'Melissa', 'Rick']
>>> lastnames = ['Watney', 'Lewis']
>>> myzip(firstnames, lastnames)
[('Mark', 'Watney'), ('Melissa', 'Lewis')]
"""

# Define function `myzip`:
# - takes two parameters `a: list`, `b: list`
# - pairs `a` elements with `b` elements in form of `list[tuple]`
# - first from `a` is paired with first from `b`, etc.
# - collect number of pairs equal to the count of shortest list
# Do not use built-in `zip()` function
# type: Callable[[list], int]
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Function Usecase AsTuple
# - Difficulty: easy
# - Lines: 2
# - Minutes: 2

# %% English
# 1. Define function `astuple`:
#    - takes `data: dict`
#    - returns tuple with `data` values
# 2. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj funkcję `astuple`:
#    - przyjmuje `data: dict`
#    - zwraca tuplę z wartościami z `data`
# 2. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert astuple is not Ellipsis, \
'Write solution inside `astuple` function'
>>> assert isfunction(astuple), \
'Object `astuple` must be a function'

>>> astuple({'a':1, 'b':2, 'c':3})
(1, 2, 3)

>>> astuple({'firstname':'Mark', 'lastname':'Watney'})
('Mark', 'Watney')
"""

# Define function `astuple`:
# - takes `data: list[int|float]`
# - returns tuple with min and max value from `data`
# Do not use built-in `min()` and `max()` functions
# type: Callable[[dict], tuple]
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Function Parameters BloodPressure
# - Difficulty: medium
# - Lines: 10
# - Minutes: 13

# %% English
# 1. Table contains Blood Pressure classification according to American Heart Association [1]
# 2. User inputs blood pressure: systolic and diastolic
# 3. User will not try to input invalid data
# 4. Print status of given blood pressure
# 5. If systolic and diastolic values are in different categories, assume worst case
# 6. Run doctests - all must succeed

# %% Polish
# 1. Tabela zawiera klasyfikację ciśnienia krwi wg American Heart Association [1]
# 2. Użytkownik wprowadza ciśnienie krwi: skurczowe i rozkurczowe
# 3. Użytkownik nie będzie próbował wprowadzać danych niepoprawnych
# 4. Wypisz status wprowadzonego ciśnienia krwi
# 5. Gdy wartości ciśnienia skurczowego i rozkurczowego należą do różnych kategorii, przyjmij gorszy przypadek
# 6. Uruchom doctesty - wszystkie muszą się powieść

# %% Hints
# - `and`
# - `or`

# %% References
# [1] Whelton, Paul K. and et al.
#     2017 ACC/AHA/AAPA/ABC/ACPM/AGS/APhA/ASH/ASPC/NMA/PCNA Guideline
#     for the Prevention, Detection, Evaluation, and Management of High
#     Blood Pressure in Adults: Executive Summary: A Report of the American
#     College of Cardiology/American Heart Association Task Force on Clinical
#     Practice Guidelines. Journal of Hypertension. vol 71. pages 1269–1324.
#     2018. doi: 10.1161/HYP.0000000000000066

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> result = blood_pressure(0,0)

>>> assert result is not Ellipsis, \
'Assign your result to variable `result`'

>>> assert type(result) is str, \
'Variable `result` has invalid type, should be str'

>>> result in (STATUS_NORMAL, STATUS_ELEVATED, STATUS_HYPERTENSION_STAGE_1,
...            STATUS_HYPERTENSION_STAGE_2, STATUS_HYPERTENSIVE_CRISIS)
True

>>> blood_pressure(115, 75)
'Normal'
>>> blood_pressure(125, 75)
'Elevated'
>>> blood_pressure(135, 85)
'Hypertension stage 1'
>>> blood_pressure(145, 95)
'Hypertension stage 2'
>>> blood_pressure(195, 135)
'Hypertensive Crisis'

>>> blood_pressure(119, 0)
'Normal'
>>> blood_pressure(120, 0)
'Elevated'
>>> blood_pressure(0, 79)
'Normal'
>>> blood_pressure(0, 80)
'Hypertension stage 1'
>>> blood_pressure(120, 80)
'Hypertension stage 1'

>>> blood_pressure(129, 0)
'Elevated'
>>> blood_pressure(130, 0)
'Hypertension stage 1'
>>> blood_pressure(0, 79)
'Normal'
>>> blood_pressure(0, 80)
'Hypertension stage 1'
>>> blood_pressure(130, 80)
'Hypertension stage 1'

>>> blood_pressure(139, 0)
'Hypertension stage 1'
>>> blood_pressure(140, 0)
'Hypertension stage 2'
>>> blood_pressure(0, 89)
'Hypertension stage 1'
>>> blood_pressure(0, 90)
'Hypertension stage 2'
>>> blood_pressure(140, 90)
'Hypertension stage 2'

>>> blood_pressure(180, 0)
'Hypertension stage 2'
>>> blood_pressure(181, 0)
'Hypertensive Crisis'
>>> blood_pressure(0, 120)
'Hypertension stage 2'
>>> blood_pressure(0, 121)
'Hypertensive Crisis'
>>> blood_pressure(181, 121)
'Hypertensive Crisis'
"""

STATUS_NORMAL = 'Normal'
STATUS_ELEVATED = 'Elevated'
STATUS_HYPERTENSION_STAGE_1 = 'Hypertension stage 1'
STATUS_HYPERTENSION_STAGE_2 = 'Hypertension stage 2'
STATUS_HYPERTENSIVE_CRISIS = 'Hypertensive Crisis'


# | Blood Pressure Category | Systolic [mm Hg] | Operator | Diastolic [mm Hg] |
# |-------------------------|------------------|----------|-------------------|
# | Normal                  | Less than 120    | and      | Less than 80      |
# | Elevated                | 120-129          | and      | Less than 80      |
# | Hypertension stage 1    | 130-139          | or       | 80-89             |
# | Hypertension stage 2    | 140 or higher    | or       | 90 or higher      |
# | Hypertensive Crisis     | Higher than 180  | and/or   | Higher than 120   |

# User inputs blood pressure: systolic and diastolic
# User will not try to input invalid data
# Print status of given blood pressure
# If systolic and diastolic values are in different categories, assume worst case
# One of the STATUS_*
# type: Callable[[int,int], str]
def blood_pressure(systolic, diastolic):
    return ...


# FIXME: Translate input data to English

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Function Arguments Clean
# - Difficulty: medium
# - Lines: 15
# - Minutes: 13

# %% English
# 1. Define function `clean`:
#    - parameter `text: str` (required)
#    - returns `str` with cleaned text
# 2. To clean text:
#    - Normalize text
#    - Remove unwanted whitespaces (eg. \n, \t, spaces, non-breaking spaces, multiple spaces)
#    - Remove unwanted special characters (e.g. !, @, #, $, %, ^, &, *, +, =, _, ', ")
#    - Remove unwanted fragments (eg. ul, ulica, os, osiedle, pl, plac, al, aleja)
#    - Replace numbers (eg. 3, 2, 1, trzeciego, drugiego, pierwszego) with Roman numerals (III, II, I)
#    - Format text (strip, capitalize first letter of each word)
# 3. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj funkcję `clean`:
#    - parametr `text: str` (wymagany)
#    - zwraca `str` z oczyszczonym tekstem
# 2. Aby oczyścić tekst:
#    - Znormalizuj tekst
#    - Usuń niechciane białe znaki (np. \n, \t, spacje, twarde space, wiele spacji)
#    - Usuń niechciane znaki specjalne (np. !, @, #, $, %, ^, &, *, +, =, _, ', ")
#    - Usuń niechciane fragmenty (np. ul, ulica, os, osiedle, pl, plac, al, aleja)
#    - Zamień liczby (np. 3, 2, 1, trzeciego, drugiego, pierwszego) na liczby rzymskie (III, II, I)
#    - Sformatuj tekst (strip, zacznij od wielkiej litery)
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert clean is not Ellipsis, \
'Write solution inside `clean` function'
>>> assert isfunction(clean), \
'Object `clean` must be a function'

>>> clean('ul.Mieszka II')
'Mieszka II'
>>> clean('UL. Zygmunta III WaZY')
'Zygmunta III Wazy'
>>> clean('  bolesława chrobrego ')
'Bolesława Chrobrego'
>>> clean('ul Jana III SobIESkiego')
'Jana III Sobieskiego'
>>> clean('\tul. Jana trzeciego Sobieskiego')
'Jana III Sobieskiego'
>>> clean('ulica Jana III Sobieskiego')
'Jana III Sobieskiego'
>>> clean('ULICA JANA III SOBIESKIEGO  ')
'Jana III Sobieskiego'
>>> clean('ULICA JANA III SOBIeskieGO')
'Jana III Sobieskiego'
>>> clean(' Jana 3 Sobieskiego  ')
'Jana III Sobieskiego'
"""

# Define function `clean`:
# - parameter `text: str` (required)
# - returns `str` with cleaned text
#
# To clean text:
# - Normalize text
# - Remove unwanted whitespaces (eg. \n, \t, spaces, non-breaking spaces, multiple spaces)
# - Remove unwanted special characters (e.g. !, @, #, $, %, ^, &, *, +, =, _, ', ")
# - Remove unwanted fragments (eg. ul, ulica, os, osiedle, pl, plac, al, aleja)
# - Replace numbers (eg. 3, 2, 1, trzeciego, drugiego, pierwszego) with Roman numerals (III, II, I)
# - Format text (strip, capitalize first letter of each word)
# type: Callable[[str], str]
...


# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Function Arguments Num2Str
# - Difficulty: medium
# - Lines: 5
# - Minutes: 8

# %% English
# 1. Given is pilot's alphabet for numbers
# 2. Convert `DATA: dict[int, str]` to `data: dict[str,str]`
#    (keys as `str`)
# 3. Define function `pilot_say` converting `int` or `float`
#    to text form in Pilot's Speak
# 4. You cannot change `DATA`, but you can modify `data`
# 5. Run doctests - all must succeed

# %% Polish
# 1. Dany jest alfabet pilotów dla numerów
# 2. Przekonwertuj `DATA: dict[int, str]` na `data: dict[str,str]`
#    (klucze jako `str`)
# 3. Zdefiniuj funkcję `pilot_say` konwertującą `int` lub `float`
#    na formę tekstową w mowie pilotów
# 4. Nie możesz zmieniać `DATA`, ale możesz modyfikować `data`
# 5. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from inspect import isfunction

>>> assert pilot_say is not Ellipsis, \
'Write solution inside `pilot_say` function'
>>> assert isfunction(pilot_say), \
'Object `pilot_say` must be a function'

>>> pilot_say(1)
'one'
>>> pilot_say(+1)
'one'
>>> pilot_say(-1)
'minus one'
>>> pilot_say(1+1)
'two'
>>> pilot_say(1-1)
'zero'
>>> pilot_say(1969)
'one niner six niner'
>>> pilot_say(31337)
'tree one tree tree seven'
>>> pilot_say(13.37)
'one tree and tree seven'
>>> pilot_say(31.337)
'tree one and tree tree seven'
>>> pilot_say(-1969)
'minus one niner six niner'
>>> pilot_say(-31.337)
'minus tree one and tree tree seven'
>>> pilot_say(-49.35)
'minus fower niner and tree fife'
>>> pilot_say(1.0)
'one and zero'
>>> pilot_say(1.)
'one and zero'
>>> pilot_say(123.)
'one two tree and zero'
>>> pilot_say(123.0)
'one two tree and zero'
>>> pilot_say(.44)
'zero and fower fower'
>>> pilot_say(1-)
Traceback (most recent call last):
SyntaxError: invalid syntax
"""

DATA = {
    0: 'zero',
    1: 'one',
    2: 'two',
    3: 'tree',
    4: 'fower',
    5: 'fife',
    6: 'six',
    7: 'seven',
    8: 'ait',
    9: 'niner',
}

# Given is pilot's alphabet for numbers
# Convert `DATA: dict[int, str]` to `data: dict[str,str]` (keys as `str`)
# Define function `pilot_say` converting `int` or `float`
# to text form in Pilot's Speak
# You cannot change `DATA`, but you can modify `data`
# type: Callable[[int|float], str]
...