Skip to content

Functional Programming

This chapter is based on the YouTube series Functional Programming in Python by Real Python and Dear Functional Bros by CodeAesthetic. It aims to give a quickoverview to beginners and reference for advanced users.

Immutable Data Structures

The credo of functional programming is "There shall be no state!". In that spirit a function should not have an inner state or should depend on some sort of state at all. Instead a given function should return the same output every time it is given the same input. It is often useful to therefor protect data (that is used in pipelines of functional code) from changes. Datatypes that cannot be changed after initialization are called immutable.

Python has several basic structures and patterns that can be used. The following table offers a sort of translation of common types or patterns and their immutable counterparts.

Structure/Pattern Immutable Counterpart
List tuple
Data Class namedtuple
for loop recursive function

Named Tuple

""" This example creates a namedtuple Person to store information about
    a person. The namedtuple is immutable, so it cannot be changed once
    it is created. The attributes are first_name, last_name, and age.
"""
from pprint import pprint
from collections import namedtuple

Person = namedtuple("Person", [
    "first_name",
    "last_name",
    "age"
])

person1 = Person("John", "Doe", 30)
person2 = Person("Jane", "Doe", 25)

pprint(person1)
print()
pprint(person2)
print()
person1.age = 31
Output:
Person(first_name='John', last_name='Doe', age=30)

Person(first_name='Jane', last_name='Doe', age=25)

Traceback (most recent call last):
  ...
    person1.age = 31
    ^^^^^^^^^^^
AttributeError: can't set attribute

Tuple

from pprint import pprint
from collections import namedtuple

Person = namedtuple("Person", [
    "first_name",
    "last_name",
    "age"
])

my_very_mutable_list = [
    Person("John", "Doe", 30),
    Person("Jane", "Doe", 25),
    Person("Mister", "Roboto", 1000)]

# this transforms a list into a tuple
pprint(tuple(my_very_mutable_list))

# but the list is still mutable...
del my_very_mutable_list[0]
print()
pprint(tuple(my_very_mutable_list))

# but the array is not even needed. just create the tuple directly with (...)!
my_very_immutable_tuple = (
    Person("John", "Doe", 30),
    Person("Jane", "Doe", 25),
    Person("Mister", "Roboto", 1000)
)
print()
pprint(my_very_immutable_tuple)

# and now it is immutable
print()
del my_very_immutable_tuple[0]
Output:
(Person(first_name='John', last_name='Doe', age=30),
 Person(first_name='Jane', last_name='Doe', age=25),
 Person(first_name='Mister', last_name='Roboto', age=1000))

(Person(first_name='Jane', last_name='Doe', age=25),
 Person(first_name='Mister', last_name='Roboto', age=1000))

(Person(first_name='John', last_name='Doe', age=30),
 Person(first_name='Jane', last_name='Doe', age=25),
 Person(first_name='Mister', last_name='Roboto', age=1000))

Traceback (most recent call last):
  ...
    del my_very_immutable_tuple[0]
        ~~~~~~~~~~~~~~~~~~~~~~~^^^
TypeError: 'tuple' object doesn't support item deletion

Loops and Filters

Loops often use some sort of internal state to remember where to continue the iteration. In functional programming this state is encoded as input to a recursive function which will return the same output for the same input (meaning the same iterable and the same state of the loop).

Often during iteration a filter is applied by using if statements. In functional programming those statements are itself functions that are then applied to the iterable to sieve out the unwanted elements before consuming the data.

Structure/Pattern Functional Counterpart
for loop recursive function
if statement filter
list comprehension generator expression

Recursive Function

from pprint import pprint
from collections import namedtuple

Person = namedtuple("Person", [
    "first_name",
    "last_name",
    "age"
])

persons = (
    Person("John", "Doe", 30),
    Person("Jane", "Doe", 25),
    Person("Mister", "Roboto", 1000)
)

# how a for loop might iterative over my_very_immutable_tuple
print("---------------------------------------------------------------")
for i in range(0, len(persons)):
    pprint(persons[i])

# but this is of course not in the spirit of functional programming
# since i is a local variable that represents the state of the loop
# instead use a resursive function


def print_tuple(iterable, index=0):
    # this is a recursion anchor (and also a guard clause, so double-cool!)
    if index < 0 or index >= len(iterable):
        return
    pprint(iterable[index])
    print_tuple(iterable, index + 1)


print("---------------------------------------------------------------")
# now call the recursive function
print_tuple(persons)

# now to take this a step further: the action taken in print_tuple is to use pprint
# this can be abstracted out into a function that takes a function as an argument


def foreach_element(iterable, consumer, index=0):
    if index < 0 or index >= len(iterable):
        return
    consumer(iterable[index])
    foreach_element(iterable, consumer, index + 1)


print("---------------------------------------------------------------")
# also prints the tuple
foreach_element(persons, pprint)

print("---------------------------------------------------------------")
# now lets do something else with the elements of the tuple by passing a different function
foreach_element(persons, lambda person: print(
    "first name is", person.first_name))
print("---------------------------------------------------------------")
Output:
---------------------------------------------------------------
Person(first_name='John', last_name='Doe', age=30)
Person(first_name='Jane', last_name='Doe', age=25)
Person(first_name='Mister', last_name='Roboto', age=1000)
---------------------------------------------------------------
Person(first_name='John', last_name='Doe', age=30)
Person(first_name='Jane', last_name='Doe', age=25)
Person(first_name='Mister', last_name='Roboto', age=1000)
---------------------------------------------------------------
Person(first_name='John', last_name='Doe', age=30)
Person(first_name='Jane', last_name='Doe', age=25)
Person(first_name='Mister', last_name='Roboto', age=1000)
---------------------------------------------------------------
first name is John
first name is Jane
first name is Mister
---------------------------------------------------------------

Filter

from pprint import pprint
from collections import namedtuple

Person = namedtuple("Person", [
    "first_name",
    "last_name",
    "age"
])

persons = (
    Person("John", "Doe", 30),
    Person("Jane", "Doe", 25),
    Person("Mister", "Roboto", 1000),
    Person("Misses", "Roboto", 9999999),
    Person("Taylor", "Swift", 22),
)

# first print all elements
print("---------------------------------------------------------------")
pprint(persons)

# print the tuple but only elements with age > 29
print("---------------------------------------------------------------")
pprint(filter(lambda person: person.age > 29, persons))

# huh, what is this? well filter returns an iterator, so we need to iterate over it
# or we just convert it to a tuple
print("---------------------------------------------------------------")
pprint(tuple(filter(lambda person: person.age > 29, persons)))
print("---------------------------------------------------------------")
Output:
---------------------------------------------------------------
(Person(first_name='John', last_name='Doe', age=30),
 Person(first_name='Jane', last_name='Doe', age=25),
 Person(first_name='Mister', last_name='Roboto', age=1000),
 Person(first_name='Misses', last_name='Roboto', age=9999999),
 Person(first_name='Taylor', last_name='Swift', age=22))
---------------------------------------------------------------
<filter object at 0x000001B196680580>
---------------------------------------------------------------
(Person(first_name='John', last_name='Doe', age=30),
 Person(first_name='Mister', last_name='Roboto', age=1000),
 Person(first_name='Misses', last_name='Roboto', age=9999999))
---------------------------------------------------------------

Generator

from pprint import pprint
from collections import namedtuple

Person = namedtuple("Person", [
    "first_name",
    "last_name",
    "age"
])

persons = (
    Person("John", "Doe", 30),
    Person("Jane", "Doe", 25),
    Person("Mister", "Roboto", 1000),
    Person("Misses", "Roboto", 9999999),
    Person("Taylor", "Swift", 22),
)

# what a list comprehension looks like
print("---------------------------------------------------------------")
pprint([x for x in persons if x.age > 29])

# wrapping the list in a tuple
print("---------------------------------------------------------------")
pprint(tuple([x for x in persons if x.age > 29]))

# and now lets get rid of the intermedtiate list
print("---------------------------------------------------------------")
pprint(tuple(x for x in persons if x.age > 29))
print("---------------------------------------------------------------")
Output:
---------------------------------------------------------------
[Person(first_name='John', last_name='Doe', age=30),
 Person(first_name='Mister', last_name='Roboto', age=1000),
 Person(first_name='Misses', last_name='Roboto', age=9999999)]
---------------------------------------------------------------
(Person(first_name='John', last_name='Doe', age=30),
 Person(first_name='Mister', last_name='Roboto', age=1000),
 Person(first_name='Misses', last_name='Roboto', age=9999999))
---------------------------------------------------------------
(Person(first_name='John', last_name='Doe', age=30),
 Person(first_name='Mister', last_name='Roboto', age=1000),
 Person(first_name='Misses', last_name='Roboto', age=9999999))
--------------------------------------------------------------