9.9. Functional First-Class

  • Function can be assigned to variable

  • Function can be stored in data structures such as hash tables, lists, ...

  • Function can be returned

  • Function can be user as a parameter

In Python, functions are considered first-class citizens, which means that they can be treated like any other object in the language. This concept is also known as first-class functions.

Here are some of the properties of first-class functions in Python:

1. A function can be assigned to a variable: You can assign a function to a variable, just like you would with any other object.

2. A function can be passed as an argument to another function: You can pass a function as an argument to another function.

3. A function can be returned as a value from another function: A function can return another function as its value.

4. A function can be stored in a data structure: You can store functions in lists, dictionaries, or other data structures.

Here's an example of using a function as a first-class citizen in Python:

>>> def add(a, b):
...     return a + b
>>>
>>> def multiply(a, b):
...     return a * b
>>>
>>> def apply(func, a, b):
...     return func(a, b)
>>>
>>> apply(add, 2, 3)
5
>>>
>>> apply(multiply, 2, 3)
6

In this example, the apply() function takes a function as its first argument, and two numbers as the second and third arguments. It then calls the function with the two numbers and returns the result. The add() and multiply() functions are passed as arguments to the apply() function, and the results are stored in result1 and result2.

9.9.1. Function as Variable

  • Function can be assigned to variable

A function can be assigned to a variable: You can assign a function to a variable, just like you would with any other object.

SetUp:

>>> def increment(x):
...     return x + 1

Usage:

>>> func = increment
>>> func(1)
2

9.9.2. Function in Data Structures

  • Function can be stored in data structures such as tuples, lists, dicts...

A function can be stored in a data structure: You can store functions in lists, dictionaries, or other data structures.

SetUp:

>>> def increment(x):
...     return x + 1
>>>
>>> def square(x):
...     return x ** 2
>>>
>>> def decrement(x):
...     return x - 1

Usage:

>>> funcs = (increment, square, decrement)

9.9.3. Functions as Return Values

  • Function can be returned

A function can be returned as a value from another function: A function can return another function as its value.

Definition:

>>> def increment(x):
...     return x + 1
>>>
>>> def get_operation():
...     return increment

Usage:

>>> func = get_operation()
>>> func(1)
2

9.9.4. Functions as Parameters

  • Function can be user as a parameter

A function can be passed as an argument to another function: You can pass a function as an argument to another function.

Definition:

>>> def increment(x):
...     return x + 1
>>>
>>> def apply(func, x):
...     return func(x)

Usage:

>>> apply(increment, 1)
2

9.9.5. Use Case - 1

>>> def map(func, data):
...     ...
>>> def filter(func, data):
...     ...
>>> def reduce(func, data):
...     ...

9.9.6. Use Case - 2

Definition:

>>> def square(x):
...     return x ** 2
>>>
>>> def apply(fn, data):
...     return fn(data)

Usage:

>>> apply(square, 2)
4

9.9.7. Use Case - 3

>>> from urllib.request import urlopen
>>>
>>>
>>> def fetch(url: str,
...           on_success = lambda response: ...,
...           on_error = lambda error: ...,
...           ) -> None:
...     try:
...         result = urlopen(url).read().decode('utf-8')
...     except Exception as error:
...         on_error(error)
...     else:
...         on_success(result)
>>> fetch(
...     url = 'https://python3.info',
...     on_success = lambda resp: print(resp),
...     on_error = lambda err: print(err),
... )
>>> def ok(response: str):
...     print(response)
>>>
>>> def err(error: Exception):
...     print(error)
>>>
>>>
>>> fetch(url='https://python3.info')
>>> fetch(url='https://python3.info', on_success=ok)
>>> fetch(url='https://python3.info', on_error=err)
>>> fetch(url='https://python3.info', on_success=ok, on_error=err)
>>> fetch(url='https://python3.info/not-existing', on_error=err)

9.9.8. Assignments

# %% About
# - Name: Functional Paradigm FirstClass
# - Difficulty: easy
# - Lines: 1
# - Minutes: 1

# %% 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

# %% English
# 1. Assign `say_hello` function to a variable
# 2. Define variable `result` with the solution
# 3. Run doctests - all must succeed

# %% Polish
# 1. Przypisz funkcję `say_hello` do zmiennej
# 2. Zdefiniuj zmienną `result` z rozwiązaniem
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Expected
# >>> result
# <function say_hello at 0x...>
#
# >>> result()
# 'hello'

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> assert 'result' in globals(), \
'Variable `result` is not defined; assign result of your program to it.'

>>> assert result is not Ellipsis, \
'Variable `result` has an invalid value; assign result of your program to it.'

>>> from inspect import isfunction
>>> assert isfunction(result), \
'Object `result` has an invalid type; expected: `function`.'

>>> result  # doctest: +ELLIPSIS
<function say_hello at 0x...>

>>> result()
'hello'
"""

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

# %% Imports

# %% Types
from typing import Callable
result: Callable[[], str]

# %% Data
def say_hello():
    return 'hello'

# %% Result
result = ...

# %% About
# - Name: Functional Paradigm FirstClass
# - Difficulty: easy
# - Lines: 1
# - Minutes: 2

# %% 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

# %% English
# 1. Create a tuple with functions `say_hello` and `say_goodbye`
# 2. Define variable `result` with the solution
# 3. Run doctests - all must succeed

# %% Polish
# 1. Utwórz krotkę z funkcjami `say_hello` i `say_goodbye`
# 2. Zdefiniuj zmienną `result` z rozwiązaniem
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Expected
# >>> result
# (<function say_hello at 0x...>, <function say_goodbye at 0x...>)

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 12), \
'Python has an is invalid version; expected: `3.12` or newer.'

>>> assert 'result' in globals(), \
'Variable `result` is not defined; assign result of your program to it.'

>>> assert result is not Ellipsis, \
'Variable `result` has an invalid value; assign result of your program to it.'

>>> assert type(result) is tuple, \
'Object `result` has an invalid type; expected: `function`.'

>>> assert len(result) == 2, \
'Variable `result` has an invalid length; expected length: `2`.'

>>> from inspect import isfunction
>>> assert all(isfunction(x) for x in result), \
'Variable `result` has elements of an invalid type; all items should be: `function`.'

>>> result  # doctest: +ELLIPSIS
(<function say_hello at 0x...>, <function say_goodbye at 0x...>)
"""

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

# %% Imports

# %% Types
from typing import Callable
type func = Callable[[], str]
result: tuple[func, func]

# %% Data
def say_hello():
    return 'hello'

def say_goodbye():
    return 'goodbye'

# %% Result
result = ...

# %% About
# - Name: Functional Paradigm FirstClass
# - Difficulty: easy
# - Lines: 4
# - Minutes: 2

# %% 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

# %% English
# 1. Modify function `greeting` and based on parameter `hour`:
#    - if `hour` < 12: return functon `say_goodmorning`
#    - else: return function `say_goodevening`
# 2. Define variable `result` with the solution
# 3. Do not validate `hour`, user will always pass an integer between 0 and 23
# 4. Run doctests - all must succeed

# %% Polish
# 1. Modify funkcji `greeting` i na podstawie parametru `hour`:
#    - jeśli `hour` < 12: zwróć funkcję `say_hello`
#    - inaczej: zwróć funkcję `say_goodbye`
# 2. Zdefiniuj zmienną `result` z rozwiązaniem
# 3. Nie waliduj `hour`, użytkownik zawsze poda liczbę całkowitą z zakresu 0-23
# 4. Uruchom doctesty - wszystkie muszą się powieść

# %% Expected
# >>> greeting(hour=11)
# <function say_goodmorning at 0x...>
#
# >>> greeting(hour=12)
# <function say_goodevening at 0x...>

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 12), \
'Python has an is invalid version; expected: `3.12` or newer.'

>>> assert 'greeting' in globals(), \
'Variable `greeting` is not defined; assign result of your program to it.'

>>> assert greeting is not Ellipsis, \
'Variable `greeting` has an invalid value; assign result of your program to it.'

>>> from inspect import isfunction
>>> assert isfunction(greeting), \
'Object `greeting` has an invalid type; expected: `function`.'

>>> greeting(hour=11)  # doctest: +ELLIPSIS
<function say_goodmorning at 0x...>

>>> greeting(hour=12)  # doctest: +ELLIPSIS
<function say_goodevening at 0x...>
"""

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

# %% Imports

# %% Types
from typing import Callable
type func = Callable[[], str]
greeting: Callable[[int], func]

# %% Data
def say_goodmorning():
    return 'good morning'

def say_goodevening():
    return 'good evening'

# %% Result
def greeting(hour: int):
    ...

# %% About
# - Name: Functional Paradigm FirstClass
# - Difficulty: easy
# - Lines: 2
# - Minutes: 2

# %% 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

# %% English
# 1. Create a function which takes a function as parameter and prints result of its execution
# 2. Define function `echo` with the result
# 3. Run doctests - all must succeed

# %% Polish
# 1. Stwórz funkcję, która przyjmuje funkcję jako parametr i wypisuje wynik jej wywołania
# 2. Zdefiniuj funkcję `echo` z rozwiązaniem
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Expected
# >>> def say_hello():
# ...    return 'hello'
#
# >>> echo(say_hello)
# hello

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0

>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'

>>> assert 'echo' in globals(), \
'Variable `echo` is not defined; assign result of your program to it.'

>>> assert echo is not Ellipsis, \
'Variable `echo` has an invalid value; assign result of your program to it.'

>>> from inspect import isfunction
>>> assert isfunction(echo), \
'Object `echo` has an invalid type; expected: `function`.'

>>> def say_hello():
...    return 'hello'

>>> echo(say_hello)
hello
"""

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

# %% Imports

# %% Types
from typing import Callable
type func = Callable[[], str]
echo: Callable[[func], None]

# %% Data

# %% Result
echo = ...