python-course.eu

10. Function Composition In Python

By Bernd Klein. Last modified: 08 Mar 2024.

In Mathematics

Composing with Legos

In this section of our Python tutorial we cover the definition of functions. This is also known in mathematics and essentially the same thing as what we will see in Python.

In Mathematics, function composition is an operation that combines two or more functions to create a new function. The composition of functions is denoted by a small circle (∘) or by simply placing one function inside another. Given two functions, f and g, the composition of these functions, denoted as (f ∘ g)(x), is defined as follows:

For two functions:

$f: A \to B$

$g: B \to C$

The composition of these functions, $(f ∘ g)$, is a new function that maps elements from set $A$ to set $C$. This composition is defined for an element $x$ in set $A$ as follows:

$(f ∘ g)(x) = f(g(x))$

In other words, to find the value of the composite function $(f ∘ g)$ at a particular input $x$, you first apply the function $g$ to $x$, and then apply the function $f$ to the result of $g(x)$.

Here's an example to illustrate function composition:

Let:

$f(x) = 2x + 3$

$g(x) = x^2$

To find $(f ∘ g)(x)$, you first apply $g$ to $x$:

$g(x) = x^2$

Then, apply $f$ to the result of $g(x)$:

$f(g(x)) = f(x^2) = 2(x^2) + 3$

So, $(f ∘ g)(x) = 2x^2 + 3$, which is the composite function of $f$ and $g$.

Live Python training

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Enrol here

Function Composition in Python

Function composition in Python is quite similiar to the mathematical concept.

Composition is the combination of two or more functions into one function, whereas currying is the process of turning a function that takes multiple arguments into a sequence of functions, each accepting one argument.

We define the composition h of two functions f and g

$h(x) = g(f(x))$

in the following Python example.

The composition of two functions is a chaining process in which the output of the inner function becomes the input of the outer function.

In Python, you can perform function composition by defining a new function that combines two or more functions to create a composite function. Here's a simple example of how to perform function composition in Python:

def f(x):
    return 2 * x

def g(x):
    return x + 3

# Define a composite function (f ∘ g)
def composite_function(x):
    return f(g(x))

# Test the composite function
result = composite_function(5)
print(result)

OUTPUT:

16

This is a simple illustration of function composition in Python. You can create more complex compositions by combining functions in a similar manner, and this technique is especially useful for building more complex operations from simpler building blocks.

Yet, there is a better way to implement the composition of to functions in a more generic way. We define a function compose which takes two functions as parameter and returns a reference to the composition of the two functions:

def compose(g, f):
    def h(x):
        return g(f(x))
    return h

We will use our compose function in the next example. Let's assume, we have a thermometer measuring degrees Celsius, which is not working accurately. The correct temperature can be calculated by applying the function readjust to the temperature values. Let us further assume that we have to convert our temperature values from Celsius to Fahrenheit. We can do this by applying compose to both functions:

def celsius2fahrenheit(t):
    return 1.8 * t + 32

def readjust(t):
    return 0.9 * t - 0.5

convert = compose(celsius2fahrenheit, readjust)

measurement_of_thermometer = 10
print(convert(measurement_of_thermometer))

OUTPUT:

47.3

Without using convert we would have to do the following:

celsius2fahrenheit(readjust(measurement_of_thermometer))

OUTPUT:

47.3

The composition of two functions is generally not commutative, i.e. compose(celsius2fahrenheit, readjust) is different from compose(readjust, celsius2fahrenheit)

convert2 = compose(readjust, celsius2fahrenheit)

print(convert2(measurement_of_thermometer))

OUTPUT:

44.5

convert2 is not a solution to our problem, because it is not readjusting the original temperatures of our thermometer but the transformed Fahrenheit values.

"compose" with Arbitrary Arguments

The function compose which we have just defined can only cope with single-argument functions. We can generalize our function compose so that it can cope with all possible functions, along with an example using a function with two parameters.

def compose(g, f):
    def h(*args, **kwargs):
        return g(f(*args, **kwargs))
    return h
def BMI(weight, height):
    return weight / height**2

def evaluate_BMI(bmi):
    if bmi < 15:
        return "Very severely underweight"
    elif bmi < 16:
        return "Severely underweight"
    elif bmi < 18.5:
        return "Underweight"
    elif bmi < 25:
        return "Normal (healthy weight)"
    elif bmi < 30:
        return "Overweight"
    elif bmi < 35:
        return "Obese Class I (Moderately obese)"
    elif bmi < 40:
        return "Obese Class II (Severely obese)"
    else:
        return "Obese Class III (Very severely obese)"


f = compose(evaluate_BMI, BMI)

again = "y"
while again == "y":
    weight = float(input("weight (kg) "))
    height = float(input("height (m) "))
    print(f(weight, height))
    again = input("Another run? (y/n)")

OUTPUT:

Normal (healthy weight)

Live Python training

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Upcoming online Courses

Enrol here

Composing an arbitrary Number of Functions

def compose(*callables):
    def composition(x):
        for callable in callables:
            x = callable(x)
        return x
    return composition
def meter2centimeter(dist):  
    """ Converting m to cm """
    return dist * 100
      
def centimeter2feet(dist): 
    """ Converting cm to ft """
    return dist / 30.48   

def feet2inches(dist):
    """ Converting ft to in """
    return dist * 12

compose(feet2inches, centimeter2feet, meter2centimeter)(1.4)

OUTPUT:

55.11811023622046

We can do the same and more elegantly with the function reduce from the module functools:

from functools import reduce

def apply(x, f):
    return f(x)

def compose(*callables):
    return lambda x: reduce(apply, callables, x)

print(compose(feet2inches, centimeter2feet, meter2centimeter)(1.4)) # => 6

OUTPUT:

55.11811023622046

Exercises

Exercise 1

Write three functions:

  1. A function converting days to hours
  2. A function converting hours to minutes
  3. A function converting minutes to seconds

Use compose to write a function to convert days to seconds.

Exercise 2

Write analogously three functions to be able to convert seconds into days by an appropriate composition function.

Live Python training

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Enrol here

Solutions

Exercise 1

We use the compose function, which we have defined in this chapter.

def compose(*callables):
    def composition(x):
        for callable in callables:
            x = callable(x)
        return x
    return composition

def days2hours(days):  
    """ A function converting days to hours. """
    return days * 24
      
def hours2minutes(hours):  
    """ A function converting hours to minutes """
    return hours * 60
      
def minutes2seconds(minutes): 
    """ A function converting minutes to seconds """
    return minutes * 60

compose(minutes2seconds, hours2minutes, days2hours)(5)

OUTPUT:

432000

Exercise 2

def hours2days(hours):  
    """ A function converting hours to days. """
    return hours / 24
      
def minutes2hours(minutes):  
    """ A function converting minutes to hours """
    return minutes / 60
      
def seconds2minutes(seconds): 
    """ A function converting seconds to minutes"""
    return seconds / 60

compose(hours2days, minutes2hours, seconds2minutes)(432000)

OUTPUT:

5.0

The next solution uses lambda to accomplish the same effect:

compose(lambda x: x/60, lambda x: x/60, lambda x: x/24)(432000)

OUTPUT:

5.0

Directly without compose and using lambda is also possible of course, but be warned: Most people would consider this as bad design:

compose(lambda x: x/60, lambda x: x/60, lambda x: x/24)(432000)

OUTPUT:

5.0

Live Python training

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Upcoming online Courses

Enrol here