Komposition von Funktionen in Python

In der Mathematik

Komposition mit Legos

In diesem Abschnitt unseres Python-Kurses behandeln wir die Konposition von Funktionen, manchmal auch als Verkettung von Funktionen bezeichnet. Dies ist auch in der Mathematik bekannt und im Wesentlichen das Gleiche wie das, was wir in Python sehen werden.

In der Mathematik ist die Funktionskomposition eine Operation, die zwei oder mehr Funktionen zu einer neuen Funktion verbindet. Die Komposition von Funktionen wird durch einen kleinen Kreis (∘) oder einfach durch die Platzierung einer Funktion in einer anderen dargestellt. Bei zwei Funktionen, f und g, ist die Komposition dieser Funktionen, bezeichnet als (f ∘ g)(x), wie folgt definiert:

$f: A \to B$

$g: B \to C$

Die Komposition dieser Funktionen, $(f ∘ g)$, ist eine neue Funktion, die Elemente aus der Menge $A$ auf die Menge $C$ abbildet. Diese Komposition ist für ein Element $x$ in der Menge $A$ wie folgt definiert:

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

Mit anderen Worten: Um den Wert der zusammengesetzten Funktion $(f ∘ g)$ bei einer bestimmten Eingabe $x$ zu finden, wendet man zunächst die Funktion $g$ auf $x$ an und wendet dann die Funktion $f$ auf das Ergebnis von $g(x)$ an.

Hier ein Beispiel zur Veranschaulichung der Funktionskomposition:

Sei:

$f(x) = 2x + 3$

$g(x) = x^2$

Um $(f ∘ g)(x)$ zu finden, wendet man zunächst $g$ auf $x$ an:

$g(x) = x^2$

Dann wendet man $f$ auf das Resultat von $g(x)$ an:

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

Also $(f ∘ g)(x) = 2x^2 + 3$, was die zusammengesetzte Funktion von $f$ und $g$ ist.

Komposition von Funktionen in Python

Die Funktionskomposition in Python ist dem mathematischen Konzept sehr ähnlich.

Komposition ist die Kombination von zwei oder mehr Funktionen zu einer Funktion, während Currying der Prozess ist, eine Funktion, die mehrere Argumente annimmt, in eine Folge von Funktionen zu verwandeln, von denen jede ein Argument annimmt.

Wir definieren die Komposition h von zwei Funktionen f und g

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

in dem folgenden Python-Beispiel.

Die Komposition von zwei Funktionen ist ein Verkettungsprozess, bei dem die Ausgabe der inneren Funktion zur Eingabe der äußeren Funktion wird.

In Python kann man eine Funktionskomposition durchführen, indem man eine neue Funktion definiert, die zwei oder mehr Funktionen kombiniert, um eine zusammengesetzte Funktion zu erstellen. Hier ein einfaches Beispiel, wie man eine Funktionskomposition in Python durchführt:

In [11]:
def f(x):
    return 2 * x

def g(x):
    return x + 3

# Definitin einer Verkettteten Funktion (f ∘ g)
def composite_function(x):
    return f(g(x))

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

Dies ist eine einfache Illustration der Funktionskomposition in Python. Man kann komplexere Kompositionen erstellen, indem man Funktionen auf ähnliche Weise kombiniert. Diese Technik ist besonders nützlich, um komplexere Operationen aus einfacheren Bausteinen aufzubauen.

Es gibt jedoch einen besseren Weg, die Komposition von zwei Funktionen auf eine allgemeinere Weise zu implementieren. Wir definieren eine Funktion compose, die zwei Funktionen als Parameter annimmt und eine Referenz auf die Komposition der beiden Funktionen zurückgibt:

In [12]:
def compose(g, f):
    def h(x):
        return g(f(x))
    return h

Im nächsten Beispiel werden wir unsere Kompositionsfunktion verwenden. Nehmen wir an, wir haben ein Thermometer, das Grad Celsius misst und nicht genau funktioniert. Die richtige Temperatur kann durch Anwendung der Funktion readjust auf die Temperaturwerte berechnet werden. Nehmen wir weiter an, dass wir unsere Temperaturwerte von Celsius in Fahrenheit umrechnen müssen. Wir können dies tun, indem wir compose auf beide Funktionen anwenden:

In [13]:
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))
47.3

Ohne die Verwendung von convert müssten wir das Folgende tun:

In [14]:
celsius2fahrenheit(readjust(measurement_of_thermometer))
Out[14]:
47.3

Die Komposition zweier Funktionen ist im Allgemeinen nicht kommutativ, d. h. compose(celsius2fahrenheit, readjust) unterscheidet sich von compose(readjust, celsius2fahrenheit)

In [15]:
convert2 = compose(readjust, celsius2fahrenheit)

print(convert2(measurement_of_thermometer))
44.5

Die Funktion convert2 ist keine Lösung für unser Problem, da sie nicht die ursprünglichen Temperaturen unseres Thermometers, sondern die umgewandelten Fahrenheit-Werte neu einstellt.

"compose" mit beliebigen Argumenten

Die Funktion Compose, die wir gerade definiert haben, kann nur mit Funktionen mit einem Parameter umgehen. Wir können unsere Funktion compose so verallgemeinern, dass sie mit beliebigen Funktionen zurechtkommt, zusammen mit einem Beispiel, das eine Funktion mit zwei Parametern verwendet.

In [16]:
def compose(g, f):
    def h(*args, **kwargs):
        return g(f(*args, **kwargs))
    return h
In [17]:
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)")
Normal (healthy weight)

Komposition einer beliebigen Anzahl von Funktionen

In [18]:
def compose(*callables):
    def composition(x):
        for callable in callables:
            x = callable(x)
        return x
    return composition
In [19]:
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)
Out[19]:
55.11811023622046

Wir können das Gleiche und noch eleganter mit der Funktion reduce aus dem Modul functools machen:

In [20]:
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
55.11811023622046

Aufgaben

Aufgabe 1

Programmiere die folgenden drei Funktionen:

  1. Eine Funktion, die Tage in Stunden umwandelt
  2. Eine Funktion, die Stunden in Minuten umrechnet
  3. Eine Funktion, die Minuten in Sekunden umwandelt

Verwende compose, um eine Funktion zu schreiben, die Tage in Sekunden umwandelt.

Aufgabe 2

Schreibe analog drei Funktionen, um durch eine geeignete Kompositionsfunktion Sekunden in Tage umwandeln zu können.

Lösungen

Lösung zu Aufgabe 1

Wir verwenden die Funktion compose, die wir in diesem Kapitel definiert haben.

In [21]:
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)
Out[21]:
432000

Lösung zu Aufgabe 2

In [22]:
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)
Out[22]:
5.0

Die nächste Lösung verwendet lambda, um den gleichen Effekt zu erzielen:

In [23]:
compose(lambda x: x/60, lambda x: x/60, lambda x: x/24)(432000)
Out[23]:
5.0

Direkt ohne compose und mit lambda ist natürlich auch möglich, aber sei gewarnt: Die meisten Leute würden dies als schlechtes Design betrachten:

In [24]:
compose(lambda x: x/60, lambda x: x/60, lambda x: x/24)(432000)
Out[24]:
5.0