4. Funktionen

Eigene Funktionen in Python definieren

Sie kennen bereits die Funktionen print sowie t.forward und haben diese auch schon mehrmals selbst verwendet. In diesem Kapitel werden Sie lernen, wie Sie eigene Funktionen in Python definieren und schliesslich sinnvoll verwenden können. Funktionen helfen enorm dabei, Programme leserlicher, kürzer, effizienter und wartbarer zu machen. Funktionen sind deshalb aus modernen Programmen kaum wegzudenken.

Beispiel

Wir betrachten nochmals den Code aus Abschnitt:

# File: blume.py
import turtle as t

for _ in range(10):
    # zeichne ein Quadrat
    for _ in range(4):
        t.fd(100)
        t.rt(360 / 4)
    # Rechtsdrehung um 36 Grad
    t.rt(360 / 10)

# Turtle-Zeichnung nicht schliessen
t.done()
Note

💡 Hinweis: Dieses Skript verwendet grafische Desktop-Bibliotheken (turtle / pygame). Führen Sie diesen Code in Ihrer lokalen Entwicklungsumgebung (z.B. VS Code) aus.

Die Blume entsteht durch das wiederholte Zeichnen eines Quadrats, wobei sich die Turtle nach jedem gezeichneten Quadrat um \(36\) Grad rotiert. Wir könnten den Code leserlicher machen, indem wir zunächst eine Funktion quadrat() definieren, welche lediglich ein Quadrat zeichnet. Danach definieren wir eine weitere Funktion quadrat_blume, welche die Funktion quadrat insgesamt 10 Mal aufrufen wird:

Die Erstellung der Definitionen einer Funktion ist vergleichbar mit dem Verfassen eines Kochrezepts (oder eines Bauplans). Die Funktion beschreibt dabei einen Programmablauf, also ein “Rezept”. Die alleinige Existenz eines Kochrezepts führt natürlich nicht zu einem fertigen Gericht. Dieses entsteht erst bei der Ausführung des Rezepts. Genauso hat in Python die Definition noch keinen Effekt. Der Effekt (die Wirkung der Funktion) erfolgt erst bei ihrem Aufruf (im obigen Beispiel erfolgen solche Funktionsaufrufe in den Zeilen 10 und 13).

Übersicht

Innerhalb des eingerückten Bereichs unter def wird eine Wirkung definiert. Diese Wirkung wird aber erst ausgelöst, wenn die Funktion aufgerufen (und somit der Code ausgeführt) wird!

Das Schreiben von Definitionen hat mehrere Vorteile:

  • Der Code wird modular: Man kann nun einfachere Funktionen schreiben, wie beispielsweise quadrat(), und komplexere Funktionen, die Unter-Funktionen aufrufen, wie beispielsweise quadrat_blume(). Auf diese Weise wird ein komplexer Code in einzelne, einfachere Teile (Module) unterteilt.
  • Der Code wird übersichtlicher: Jede Teil-Aktivität ist eine Funktion
  • Falls das komplexere Programm nicht das gewünschte Resultat liefert, können wir es debuggen, indem wir die einfacheren Funktionen zuerst aufrufen und sicherstellen, dass diese richtig funktionieren.
Beispiel

Die folgende Gegenüberstellung illustriert, wie Funktionen dabei helfen, Programme übersichtlicher zu machen und Struktur in den Code zu bringen:

Tipps für das Schreiben von Funktionen:

  • Der Name einer Funktion sollte selbsterklärend und beschreibend sein. Im Idealfall lässt der Name der Funktion schon viel über ihren Effekt erahnen. Beispielsweise ist der Funktionsname quadrat viel beschreibender als ein nichtssagender Name wie zum Beispiel f.
  • Etwas komplexere Tätigkeiten sollten jeweils als eine Funktion definiert werden und damit einen Namen erhalten. So kann beispielsweise das Zeichnen eines Quadrats in einer Funktion quadrat definiert werden, während das Zeichnen einer Blume in der Funktion quadrat_blume definiert wird. Diese Funktion kann dann wiederum die Funktion quadrat aufrufen.
Aufgabe

Führen Sie den folgenden Code zuerst aus und betrachten Sie das Resultat. Schreiben Sie den Code um, indem Sie zwei Funktionen schreiben:

  1. viertelkreis
  2. abgerundetes_quadrat
import turtle as t

for _ in range(4):
    for _ in range(9):
        t.fd(4)
        t.lt(10)
    t.fd(100)

t.done()
Note

💡 Hinweis: Dieses Skript verwendet grafische Desktop-Bibliotheken (turtle / pygame). Führen Sie diesen Code in Ihrer lokalen Entwicklungsumgebung (z.B. VS Code) aus.

\end{figure}

Aufgabe

Packen Sie Ihre Lösung aus Abschnitt in eine Funktion mit dem Namen haus. Testen Sie zunächst, ob die Funktion wirklich das gewünschte Resultat liefert (ein einzelnes Haus), indem Sie die Funktion aufrufen. Schreiben Sie dann eine zweite Funktion haeuserreihe, welche eine Häuserreihe aus 5 Häusern zeichnet, indem haus fünfmal aufgerufen wird.

Ihr Code sollte folgendes Bild ausgeben:

Siehe den Code in Abschnitt.

Parameter

Stellen Sie sich vor, Sie wollen drei unterschiedlich grosse Quadrate zeichnen. Sie könnten folgendermassen vorgehen:

# File: squares_noparams.py
import turtle as t

def quadrat_50():
    for _ in range(4):
        t.fd(50)
        t.rt(90)

def quadrat_100():
    for _ in range(4):
        t.fd(100)
        t.rt(90)

def quadrat_150():
    for _ in range(4):
        t.fd(150)
        t.rt(90)

quadrat_50()
quadrat_100()
quadrat_150()
Note

💡 Hinweis: Dieses Skript verwendet grafische Desktop-Bibliotheken (turtle / pygame). Führen Sie diesen Code in Ihrer lokalen Entwicklungsumgebung (z.B. VS Code) aus.

Das ging gerade noch! Was jedoch, wenn Sie 20 unterschiedlich grosse Quadrate zeichnen wollen? Brauchen Sie dann 20 Definitionen? Zum Glück nicht, wie Beispiel Abschnitt zeigt.

Beispiel

Folgendes Beispiel verwendet einen Parameter laenge, um die Länge des Quadrats jedes Mal anzupassen:

Funktionen können mehrere (oder auch gar keine) Parameter haben. Ersteres ist besonders nützlich, wenn Sie eine Funktion schreiben wollen, die mehrere Werte benötigt, um ihre Aufgabe zu erfüllen. Zum Beispiel könnte eine Funktion, die ein Vieleck zeichnet, sowohl die Anzahl der Ecken als auch die Farbe des Vielecks benötigen.

Beispiel

In folgendem Beispiel kann nicht nur die Anzahl Ecken, sondern auch die Farbe eines Vielecks übergeben werden.

# File: farb_vieleck.py
import turtle as t

def farb_vieleck(ecken, farbe):
    t.color(farbe)
    for _ in range(ecken):
        t.fd(50)
        t.rt(360 / ecken)

farb_vieleck(3, "red")
farb_vieleck(6, "green")
farb_vieleck(4, "blue")

t.done()
Note

💡 Hinweis: Dieses Skript verwendet grafische Desktop-Bibliotheken (turtle / pygame). Führen Sie diesen Code in Ihrer lokalen Entwicklungsumgebung (z.B. VS Code) aus.

Begrifflichkeiten bei Python-Funktionen

Wir haben in diesem Kapitel mehrere wichtige Begriffe im Zusammenhang mit Funktionen in Python kennengelernt. Diese Begriffe wollen wir hier anhand eines Beispiels übersichtlich darstellen:

Die allgemeine Form einer Funktion in Python sieht also so aus:

Aufgabe

Ändern Sie den Code aus Abschnitt so ab, dass zusätzlich zur Farbe und der Anzahl Ecken auch noch die Stiftdicke für jedes Vieleck verändert werden kann. Verwenden Sie dazu einen weiteren Parameter sowie die Funktion t.width().

Aufgabe

Erstellen Sie die Funktionen mit den folgenden Signaturen:

  • def summiere(x1, x2): Berechnet die Summe der zwei Parameter x1 und x2 und gibt das Resultat mittels print im Terminal aus.
  • def summe_quadrate(x, y): Berechnet die Summe (x^2 + y^2) und gibt diese mittels im Terminal aus.

Lebensdauer (*scope

) einer Variablen Im Code aus Abschnitt wird bei jedem Aufruf* der Definition quadrat eine neue Variable laenge erstellt, welche nur innerhalb der Funktion existiert und welche jedes Mal einen anderen Wert hat. Den Wert erhält die Variable zum Zeitpunkt des Aufrufs der Funktion, also auf den Zeilen 8, 9 und 10!

Die Variable laenge existiert also nur innerhalb der Definition quadrat. Wenn wir nach Zeile 6 im Hauptprogramm (nicht eingerückt) den Befehl print(laenge) eingeben würden, würden wir eine Fehlermeldung erhalten, da es die Variable nicht mehr gibt. Parameter sind also immer lokale Variablen, ebenso wie Variablen, welche wir innerhalb von Funktionen erstellen. Diese Variablen “sterben” nach Ausführung der Definition, daher spricht man auch von der Lebensdauer einer Variable, bzw. von deren Reichweite (Englisch: scope).

Variablen, welche im Hauptprogramm (nicht eingerückt) erstellt werden, sind sogenannte globale Variablen.

Aufgabe

Beschreiben Sie, welche der Variablen im unten stehenden Code lokal oder global sind, und welche Variablen auch Parameter sind. Sie sollten insgesamt 5 Variablen finden!

# File: houses.py
import turtle as t
import math

def haus(laenge):
    anzahl_mauern = 4
    # Mauern
    for _ in range(anzahl_mauern):
        t.fd(laenge)
        t.lt(90)

    # Bewegung zu Dach hin
    t.lt(90)
    t.fd(laenge)

    # Dach
    t.rt(45)
    t.fd(laenge / math.sqrt(2))
    t.rt(90)
    t.fd(laenge / math.sqrt(2))
    t.rt(45)
    t.fd(laenge)
    t.lt(90)

def haeuserreihe(laenge_pro_haus):
    anzahl_haeuser = 5
    for _ in range(anzahl_haeuser):
        haus(laenge_pro_haus)

seitenlaenge = 50
haeuserreihe(seitenlaenge)

# Zeichnung stehen lassen
t.done()
Note

💡 Hinweis: Dieses Skript verwendet grafische Desktop-Bibliotheken (turtle / pygame). Führen Sie diesen Code in Ihrer lokalen Entwicklungsumgebung (z.B. VS Code) aus.

  1. laenge: lokale Variable, Parameter der Funktion haus
  2. anzahl_mauern: lokale Variable
  3. laenge_pro_haus: lokale Variable, Parameter der Funktion haeuserreihe
  4. anzahl_haeuser: lokale Variable
  5. seitenlaenge: globale Variable
Aufgabe

Was könnte der Vorteil von lokalen Variablen sein?

Falls Sie mehrere Funktionen schreiben, können Sie dieselben Variabelnamen und Parameter-Namen innerhalb der Funktionen verwenden, da diese nichts miteinander zu tun haben. Im Code aus Abschnitt hätten wir auch in beiden Funktionen laenge schreiben können.

Werte zurückgeben mit return

Einzelne Funktionen

Wie Sie bereits wissen, können Definitionen mit Kochrezepten verglichen werden: Sie beschreiben einen Ablauf, ohne diesen auszuführen. Damit der Code einer Funktion ausgeführt wird, müssen Sie die Definition aufrufen: In Abschnitt wurde dies beispielsweise mit farb_vieleck(3, "red") gemacht. Erst aufgrund dieser Zeile wurde etwas gezeichnet!

Dies kann nützlich sein, wenn sie eine Aufgabe mehrmals und in unterschiedlichen Varianten ausführen wollen, wie beispielsweise in Abschnitt.

Eine wichtige Limitation von Definitionen war bisher, dass Sie zwar Dinge berechnen und im Terminal ausgeben konnten, allerdings verschwinden alle Parameter und lokalen Variablen nach der Ausführung einer Funktion. Dies bedeutet, dass alle Variablen, die wir je in einer Definition erstellt haben, nur in dieser Definition “lebten”. Wir konnten jedoch nicht mehr auf Parameter oder lokale Variablen zugreifen, nachdem die Funktion beendet wurde.

Beispiel

Betrachten Sie folgenden Code. Weshalb führt er zu einer Fehlermeldung?

Das Terminal gibt folgende Meldung aus: [Zeile: 5] NameError: name 'summe' is not defined. Dies bedeutet, dass der Ausdruck / der Name summe auf Zeile 5 nicht existiert. Die Fehlermeldung erklärt sich dadurch, dass alle Variablen, die innerhalb einer Definition erstellt werden, nur innerhalb dieser Definition “leben”, also existieren. Dass Variablen mittels dem Befehl return auch an das Hauptprogramm “zurückgegeben” werden können, lernen wir in diesem Kapitel.

Funktionen können jedoch auch Werte an das Hauptprogramm zurückgeben: In diesem Kapitel lernen wir, wie Funktionen, ähnlich wie “Sous-Chefs” (Hilfs-Köche) in einer komplexen Küche Zutaten (Parameter) entgegennehmen und Resultate an andere “Sous-Chefs” (Funktionen) via das Hauptprogramm weitergeben werden können.

Eine Funktion kann man sich vorstellen wie ein Kochrezept, das einige Zutaten entgegennimmt und ein Resultat (Gericht) zurückgibt. Die Zutaten, oder “Inputs”, werden dabei häufig als “Parameter” bezeichnet, währenddem das fertige Gericht, oder “Resultat” häufig als “return-Wert” bezeichnet wird. Diese Idee ist in Abschnitt veranschaulicht.

Beispiel

Betrachten Sie nochmals den folgenden, leicht modifizierten Code aus Abschnitt. Wenn wir den Wert mit return an das Hauptprogramm zurückgeben und in einer Variable (im Hauptprogramm, auf Zeile 5) abspeichern, funktioniert der Code nun: Wir können auch ausserhalb der Funktion summiere() auf einen Wert zugreifen, welcher innerhalb der Definition berechnet wurde, und diesen potentiell weiterverwenden.

Wichtiger Hinweis

Man muss sich diesen Code wie folgt vorstellen: Auf Zeile 7 wird die Funktion summiere aufgerufen, die Werte 3 und 5 werden für die Parameter x1 und x2 übergeben. Zeile 2 berechnet nun die Summe dieser beiden Werte. Auf Zeile 3 wird nicht die Variable summe an das Hauptprogramm zurückgegeben, sondern deren Wert (in diesem Fall: 8). Dies ist sehr wichtig: Nur weil wir return summe schreiben, heisst das nicht, dass wir ausserhalb der Funktion summiere auf eine Variable summe zugreifen können. Vielmehr kann man sich vorstellen, dass der blau markierte Bereich auf Zeile 7 nun “ersetzt” wird durch den Wert 8, welcher vom Unterprogramm an das Hauptprogramm zurückgegeben wird. Zeile 7 liest sich also nach der Ausführung des Unterprogramms so als ob man res = 8 geschrieben hätte. Man könnte selbstverständlich auch einen anderen Variablenname statt res auswählen, z.B. x, y, resultat (kurz res) oder einfach summe. In letzterem Falle wäre summe auf Zeile 7 eine globale Variable und somit gänzlich unterschiedlich von der lokalen Variable summe auf Zeile 2.

Aufgabe

Gegeben seien die Höhe h und die Grundseite b eines Dreiecks. Schreiben Sie eine Funktion flaeche_dreieck(h, b), welche den Flächeninhalt dieses Dreiecks berechnet und mit return zurückgibt. Testen Sie die Funktion mit den Werten h=5 und b=10, speichern Sie das Resultat in einer Variable result und geben Sie deren Wert per print nach dem Funktionsaufruf (im Hauptprogramm) aus.

Aufgabe

Schreiben Sie ein Programm, das die Funktion flaeche_dreieck(h, b) aus Abschnitt verwendet, um den Flächeninhalt der folgenden Wurzelspirale zu berechnen.

Aufgabe

Gegeben sei eine ganze Zahl (n). Schreiben Sie eine Funktion square(n), welche das Quadrat von (n) berechnet und mit return zurückgibt. Testen Sie die Funktion mit dem Wert 5, speichern Sie das Resultat in einer Variable result und geben Sie deren Wert per print nach dem Funktionsaufruf (im Hauptprogramm) aus.

Aufgabe

In welchen Fällen ist es sinnvoller, einen Wert einfach per print auf der Konsole auszugeben, und wann ist ein return-Befehl sinnvoller? Ist das Zurückgeben des Werts in Abschnitt und Abschnitt sinnvoll, oder würde hier auch ein print reichen?

Aufgabe print return
Abschnitt
Abschnitt

Begründen Sie Ihre Antwort:

Aufgabe print return
Abschnitt
Abschnitt

return-Ausdrücke ergeben vor allem dann Sinn, wenn mit einem Wert, welcher in einer Funktion berechnet wurde, ausserhalb der Funktion weitergerechnet werden soll. In allen anderen Fällen reicht ein print meist aus. Auch bei Abschnitt und Abschnitt würde ein einfaches print reichen.

Mehrere Funktionen

Wie Sie in Abschnitt gesehen haben, ist ein return in vielen Fällen nicht notwendig, ein einfaches print reicht in vielen Fällen aus. Wozu kann ein return eigentlich nützlich sein? Wenn Sie Code schreiben, wird dieser oftmals schnell komplizierter. Somit kann es hilfreich sein, die einzelnen Schritte in Unter-Programme aufzuteilen. Dies erleichtert zudem die Modularisierung und Wiederverwendbarkeit von Code: Beispielsweise könnte eine Funktion, die das Maximum einer Liste berechnet, an verschiedenen Orten in einem längeren Programm mehrmals zum Einsatz kommen.

Zum Vergleich: stellen Sie sich vor, Sie arbeiten in einer edlen Michelin-Sterne-Küche an einem raffinierten Gericht, in das viele Arbeitsschritte involviert sind. Häufig müssen in solchen Restaurants bis zu 200 Schritte pro Gericht ausgeführt werden. Daher könnte es sinnvoll sein, einzelne Aufgaben, wie beispielsweise die Herstellung der Saucen, an Ihre Sous-Chefs zu delegieren, und eine Person zu bestimmen, die das ganze Gericht zusammenfügt. Häufig werden Funktionen genau mit solchen komplexen Aufgaben im Hinterkopf geschrieben. Diese Idee ist in Abschnitt illustriert.

In Abschnitt haben wir eine Funktion a1, die zwei Parameter entgegennimmt (x1 und x2). Mit diesen Parametern macht sie etwas (z.B. die Summe berechnen, ein Quadrat zeichnen oder irgend eine sonstige Aufgabe) und gibt danach einen neuen, berechneten Wert a1 zurück. Dieser kann im Hauptprogramm abgespeichert und an andere Funktionen weitergegeben werden, beispielsweise an die Funktion a3.

Beispiel

Stellen Sie sich vor, Sie arbeiten für einen Supermarkt und sollten den Preis Ihrer Produkte berechnen. Sie kaufen Ihre Artikel bei einem Grossverteiler ein, daher erhalten Sie auf alle Produkte 10 % Rabatt. Der Grossverteiler befindet sich im Ausland, Sie müssen also noch 7 % Mehrwertsteuer hinzufügen. Der Grossverteiler gibt Ihnen den Original-Preis Ihrer Waren vor Rabatt und vor Mehrwertsteuer.

Folgender Code berechnet den Endpreis, für den Sie ihre Waren verkaufen werden, indem folgende zwei Funktionen aufgerufen werden:

  1. berechne_rabatt berechnet den Preis nach Abzug eines Rabatts für ein Produkt.
  2. berechne_gesamtpreis ruft berechne_rabatt auf und fügt zum rabattierten Preis die Mehrwertsteuer hinzu.

Mit folgendem Code können wir die Turtle eine Spirale aus mehreren Vielecken zeichnen lassen:

# File: spirale_kreis.py
import turtle as t

# don't show turtle moving
t.tracer(False)

def vieleck(umfang, ecken):
    for _ in range(ecken):
        t.fd(umfang / ecken)
        t.rt(360 / ecken)

def vieleck_muster(umfang, ecken):
    for _ in range(60):
        vieleck(umfang, ecken)
        t.rt(6)
        umfang -= 10

vieleck_muster(600, 36)
t.done()
Note

💡 Hinweis: Dieses Skript verwendet grafische Desktop-Bibliotheken (turtle / pygame). Führen Sie diesen Code in Ihrer lokalen Entwicklungsumgebung (z.B. VS Code) aus.

Beispiel

Leider ist unsere Turtle etwas müde und sollte nicht zu viel laufen. Daher möchten wir fortlaufend die Gesamtdistanz berechnen, um am Ende herauszufinden, ob unsere Turtle zu viel Strecke zurückgelegt hat. Dies können wir tun, indem wir die Funktion einen Wert zurückgeben lassen:

import turtle as t

def vieleck(umfang, ecken):
    for _ in range(ecken):
        t.fd(umfang/ecken)
        t.rt(360/ecken)

def vieleck_muster(umfang, ecken):
    (*@\colorbox{CornflowerBlue}{gesamtdistanz=0}@*)
    for _ in range(60):
        vieleck(umfang, ecken)
        (*@\colorbox{CornflowerBlue}{gesamtdistanz+=umfang}@*)
        umfang-=10
        t.rt(10)
    (*@\colorbox{CornflowerBlue}{return gesamtdistanz}@*)
    
(*@\colorbox{CornflowerBlue}{gesamtdistanz = }@*)vieleck_muster(600,35)
(*@\colorbox{CornflowerBlue}{print(gesamtdistanz)}@*)
Note

💡 Hinweis: Dieses Skript verwendet grafische Desktop-Bibliotheken (turtle / pygame). Führen Sie diesen Code in Ihrer lokalen Entwicklungsumgebung (z.B. VS Code) aus.

Aufgabe

Schreiben Sie eine Python-Funktion def notenskala(maxPunkte, erreichtePunkte), welche die erreichte Note in Abhängigkeit der maximal möglichen Punktzahl und der erreichten Punktzahl berechnet und per return zurückgibt. Verwenden Sie dafür die folgende Formel:

[ = + 1 ]

Aufgabe

Schreiben Sie ein Programm, das Notenschnitte berechnet. Als erstes soll das Programm den Nutzer Fragen, wie viele Prüfungen stattgefunden haben. Anschliessend soll der Benutzer für jede Prüfung die erreichte und die maximale Punktzahl eingeben.

Das Programm gibt dann die einzelnen Prüfungsnoten (berechnet mit der Funktion aus Abschnitt) und den Gesamtschnitt im Terminal aus.

Formel 1

Ein Formel-1-Auto fährt eine bestimmte Strecke in einer bestimmten Zeit. Schreiben Sie eine Funktion durchschnittsgeschwindigkeit(strecke_km, zeit_min), die Strecke in Kilometern und Zeit in Minuten als Eingabe erhält und die durchschnittliche Geschwindigkeit in km/h per return zurückgibt.

Formel: Durchschnittsgeschwindigkeit = Strecke (km) / Zeit (h)

Boxenstopp

Ein Formel-1-Rennteam möchte die Gesamtzeit eines Rennens inklusive Boxenstopps berechnen. Gegeben sind die Gesamtstrecke Km, die Durchschnittsgeschwindigkeit in km/h, die Anzahl Stopps sowie die Dauer pro Stopp in Sekunden.

Schreiben Sie zwei Funktionen:

def fahrzeit_ohne_stopps(strecke, kmh): Berechnet die reine Fahrzeit in Sekunden und gibt diese per return zurück.

def gesamtzeit(strecke, kmh, stopps, stoppdauer): Berechnet zuerst die Fahrzeit ohne Stopps (mit der ersten Funktion) und berechnet dann die Gesamtzeit mit Stopps, indem zur Fahrzeit ohne Stopps die Anzahl Boxenstopps mal die Dauer eines Boxenstopps (in Sekunden) hinzugefügt wird. Das Resultat soll ebenfalls per return zurückgegeben werden.

Formel 1: Fahrzeit (Sekunden) = Strecke (km) / Geschindigkeit (km/h) * 3600

Formel 2: Gesamtzeit (Sekunden) = Fahrzeit + (Anzahl Stopps \(\times\) Stoppdauer)

Tipp 1: Schreiben Sie den Code zuerst in VS Code und testen sie ihn erst nachher auf Moodle.

Tipp 2: Schreiben Sie zuerst die erste Funktion und testen Sie diese mit folgenden Testwerten: Geschwindigkeit = 210 km/h, Strecke = 305 km. Das Resultat sollte sein: 5228.57 Sekunden. Schreiben Sie erst danach die zweite Definition, und rufen Sie darin die erste Definition auf.