Vorwissen: Python Grundlagen (Variablen, Verzweigungen mit elif, Zufallszahlen, Format-Strings)
Lernziele. Nach diesem Kapitel kannst du …
return), und erklären, worin sich return von print unterscheidet.assert testen.
Mit einer while-Schleife wiederholst du einen Codeblock direkt hintereinander. Oft brauchst du aber den gleichen Codeblock an verschiedenen Stellen im Programm. Bisher bleibt dir dann nur Copy-Paste – und Copy-Paste heisst: Jeder Fehler steckt danach mehrfach im Code.
Mit einer Funktion gibst du einem Codeblock einen Namen. Danach kannst du ihn überall mit diesem Namen aufrufen. Das hat drei Vorteile:
turn_around() sagt sofort, was passiert. Zwei kara.turnLeft() muss man erst entschlüsseln.Kara kennt nur wenige Befehle, zum Beispiel keinen zum Umdrehen. Den bauen wir uns selbst:
def (von engl. define). Darauf folgen der Funktionsname, runde Klammern () und ein Doppelpunkt :._ getrennt, z.B. turn_around. Diese Schreibweise heisst snake_case und ist in Python üblich. Wir verwenden englische Namen.turnLeft sind dagegen in camelCase geschrieben (Wortanfänge gross). Das stammt aus der Programmiersprache Java, in der Kara ursprünglich entwickelt wurde. Für eigene Funktionen halten wir uns an die Python-Konvention.
Die Definition allein bewirkt noch nichts: Python merkt sich nur, dass es eine Funktion turn_around gibt. Erst der Aufruf turn_around() führt den Funktionskörper aus. Dabei passiert Folgendes:
Daraus folgen zwei Regeln:
turn_around() ruft die Funktion auf. turn_around ohne Klammern tut gar nichts, und zwar ohne Fehlermeldung!Teil 1: Lies den folgenden Code, ohne ihn auszuführen. Schreibe auf Papier auf, was ausgegeben wird. Führe ihn erst dann aus und vergleiche.
Teil 2: Was passiert, wenn du die Zeile print("A") samt den Zeilen darunter über die Funktionsdefinition verschiebst? Sag es zuerst voraus, dann probiere es aus. Lies die Fehlermeldung genau!
Kara soll die Treppe hinunter zum Pilz. Das Hauptprogramm ist schon fertig: Es ruft die Funktion step() so lange auf, bis Kara vor dem Pilz steht. Nur: Die Funktion step() gibt es noch nicht.
Schreibe die Funktion step(), die Kara genau eine Stufe hinunterbringt. Danach soll Kara wieder nach rechts schauen. Das Hauptprogramm darfst du nicht verändern.
Beachte: Hier hast du zuerst benutzt und dann gebaut: Das Hauptprogramm beschreibt die Lösung mit einem Befehl, den es noch gar nicht gibt. So gehen Programmierer:innen auch bei grossen Problemen vor.
turn_around() macht immer genau dasselbe. Oft soll eine Funktion aber leicht unterschiedliche Dinge tun, z.B. unterschiedlich viele Kleeblätter legen. Dazu gibt man der Funktion beim Aufruf eine Information mit:
length in der Definition heisst Parameter. Er funktioniert in der Funktion wie eine gewöhnliche Variable.5 im Aufruf heisst Argument. Beim Aufruf erhält der Parameter den Wert des Arguments: length = 5.def greet(name, town):. Beim Aufruf werden die Argumente der Reihe nach zugewiesen: greet("Oskar", "Amriswil") ergibt name = "Oskar" und town = "Amriswil".
#### Aufgabe K3: Walk the Line
Schreibe eine Funktion walk(steps), die Kara steps Felder vorwärts bewegt. Das Hauptprogramm ist vorgegeben und soll Kara damit vor den Pilz bringen.
Verwende die Funktion draw_leaves(length) von oben, um eine Funktion draw_square(size) zu schreiben. Sie soll ein Quadrat aus Kleeblättern legen, dessen Seiten je size Kleeblätter lang sind. Am Schluss soll Kara wieder auf dem Startfeld stehen und nach rechts schauen.
Eine Funktion darf also selbst andere Funktionen aufrufen!
Schreibe eine Funktion greet(name, town), die eine Person herzlich begrüsst, z.B. so:
Hallo, mein lieber Oskar aus Amriswil, ich wünsche dir einen ganz tollen Tag!
Rufe die Funktion für drei verschiedene Personen auf.
Du verwendest schon lange Funktionen, die dir eine Antwort geben: kara.treeFront() liefert True oder False zurück, und diese Antwort verwendest du in if und while. Es gibt also zwei Arten von Funktionen:
| Art | Beispiel | Resultat |
|---|---|---|
| Befehl | kara.move(), turn_around() | tut etwas, gibt aber nichts zurück |
| Abfrage | kara.treeFront(), random.randint(1, 6) | berechnet einen Wert und gibt ihn mit return zurück |
Mit return schreibst du eigene Abfragen. Beispiel: Kara läuft so lange, bis sie in einem Tunnel steht, also links und rechts ein Baum ist:
Der Aufruf in_tunnel() wird durch den zurückgegebenen Wert ersetzt, also durch True oder False. Genauso funktioniert es mit Zahlen:
return beendet die Funktion sofort. Code nach einem ausgeführten return wird nicht mehr ausgeführt. Deshalb kann eine Funktion mehrere return haben, z.B. in verschiedenen Zweigen einer Verzweigung:
Kara läuft durch einen Gang und soll in die erste Abzweigung einbiegen. Schreibe die Abfrage side_free(). Sie soll True zurückgeben, wenn links oder rechts von Kara kein Baum steht, sonst False. Das Hauptprogramm ist vorgegeben.
Schreibe eine Funktion head_or_tail(), die einen Münzwurf simuliert: Sie soll zufällig "Kopf" oder "Zahl" zurückgeben. Die Funktion selbst gibt nichts aus! Die Ausgabe erfolgt im Hauptprogramm.
Simuliere im Hauptprogramm 20 Münzwürfe und gib jedes Resultat aus. Zusatz: Zähle mit, wie oft "Kopf" kam.
Das ist der klassische Stolperstein bei Funktionen:
print(x) | return x |
|
|---|---|---|
| Wohin geht der Wert? | auf den Bildschirm – für Menschen | zurück an den Aufrufer – fürs Programm |
| Kann man damit weiterrechnen? | nein | ja: y = f(3) + 1 |
| Was liefert der Aufruf? | None (nichts) | den Wert x |
Faustregel: Eine Funktion, die etwas berechnet, gibt das Resultat mit return zurück. Ausgegeben wird im Hauptprogramm. So kann man die Funktion überall wiederverwenden, auch dort, wo man das Resultat gar nicht anzeigen will.
Was gibt dieses Programm aus? Schreib es zuerst auf Papier auf, dann führe es aus.
Funktionen mit Rückgabewert haben einen grossen Vorteil: Man kann sie automatisch testen. Die Zeile
assert volume_cube(2) == 8
prüft, ob deine Funktion für die Seitenlänge 2 wirklich 8 zurückgibt. Stimmt etwas nicht, gibt es einen AssertionError. Die Tests dieser Aufgabe laufen automatisch mit. Schreibe die folgenden Funktionen. Alle sollen ihr Resultat zurückgeben, nicht ausgeben:
volume_cube(x): Volumen eines Würfels mit Seitenlänge x.volume_sphere(r): Volumen einer Kugel mit Radius r: $V = \frac{4\pi}{3}\cdot r^3$. Die Zahl $\pi$ erhältst du mit math.pi.pythagoras(a, b): Länge der Hypotenuse eines rechtwinkligen Dreiecks mit den Katheten a und b. Die Wurzel ziehst du mit math.sqrt(x).Gib danach im Hauptprogramm aus: Welches Volumen hat ein Würfel mit 13 cm Seitenlänge?
Die Fakultät einer natürlichen Zahl $n$ ist das Produkt aller natürlichen Zahlen von $1$ bis $n$. Man schreibt sie mit einem Ausrufezeichen: $5! = 1 \cdot 2 \cdot 3 \cdot 4 \cdot 5 = 120$. Per Definition ist $0! = 1$.
Schreibe eine Funktion factorial(n), die $n!$ zurückgibt.
Challenge für absolute Freaks: Kannst du die Fakultät ganz ohne Schleife berechnen? Tipp: $5! = 5 \cdot 4!$. Dann hast du das Prinzip der Rekursion entdeckt: Eine Funktion ruft sich selbst auf!
Variablen, die innerhalb einer Funktion entstehen, sind lokal: Es gibt sie nur, während die Funktion läuft. Von aussen sind sie unsichtbar.
Auch Parameter sind lokal. Ändert die Funktion ihren Parameter, bleibt die Variable des Aufrufers unverändert:
Das ist kein Nachteil, sondern genau der Sinn der Sache: Eine Funktion ist eine abgeschlossene Blackbox. Was hineingeht, sind die Argumente, was herauskommt, ist der Rückgabewert. Alles dazwischen bleibt drinnen.
Und wenn eine Funktion etwas zählen soll? Dann zählt sie mit einer lokalen Variablen und gibt das Resultat mit return zurück. Versucht sie dagegen, eine Variable von ausserhalb zu verändern, gibt es einen Fehler (UnboundLocalError). Das zeigt die nächste Aufgabe.
Kara soll bis zum Baum laufen und zählen, auf wie vielen Feldern ein Kleeblatt liegt. Der vorgegebene Code funktioniert aber nicht.
count_leaves(): Sie soll mit einer lokalen Variablen zählen und die Anzahl zurückgeben. Das Hauptprogramm speichert das Resultat in n und gibt es aus.| Fehler | Was passiert | Lösung |
|---|---|---|
| Funktion aufgerufen, bevor sie definiert ist | NameError: name '…' is not defined | Definitionen nach oben, Hauptprogramm nach unten |
Klammern beim Aufruf vergessen: turn_around | gar nichts, auch keine Fehlermeldung! | turn_around() |
Falsche Anzahl Argumente: walk() statt walk(3) | TypeError: … missing 1 required positional argument | Definition und Aufruf vergleichen |
return vergessen | Aufruf liefert None | Resultat mit return zurückgeben |
print statt return | Wert erscheint auf dem Bildschirm, ist aber für das Programm verloren | siehe print oder return? |
Doppelpunkt nach def …() vergessen | SyntaxError | def name(): |
Im folgenden Programm stecken vier Fehler. Behebe sie einen nach dem anderen. Lies dazu jedes Mal die Fehlermeldung und die Zeilennummer genau! Die Tests laufen erst durch, wenn alle Fehler behoben sind.
Die meisten Würfel haben 6 Seiten, manche aber 12 oder 20. Mit einem Vorgabewert (engl. default value) muss man die Seitenzahl nur angeben, wenn sie von 6 abweicht:
Wird für sides kein Argument übergeben, gilt sides = 6. Üblich ist, beim Vorgabewert keine Leerzeichen um das = zu setzen.
Schreibe eine Funktion greeting(name, word="Hallo"), die den Gruss als String zurückgibt. Er soll aus dem Grusswort, einem Komma, dem Namen und einem Ausrufezeichen bestehen:
- greeting("Oskar") gibt "Hallo, Oskar!" zurück.
- greeting("Oskar", "Grüezi") gibt "Grüezi, Oskar!" zurück.
Vergleiche mit Aufgabe K5: Warum ist diese Version vielseitiger?
Nützliche Funktionen willst du nicht in jedes Programm neu kopieren. Stattdessen legst du sie in eine eigene Datei, z.B. toolbox.py. Das ist dein Werkzeugkasten, und mit import holst du die Werkzeuge ins Hauptprogramm:
from toolbox import draw_square, walk # holt genau diese zwei Funktionen
Das kennst du schon: import random holt Pythons Werkzeugkasten für Zufallszahlen.
draw_leaves(length) und draw_square(size) aus K4 sowie walk(steps) aus K3 in die Datei toolbox.py.main.py ein Programm, das fünf Quadrate der Grösse 3 nebeneinander zeichnet, mit je einem leeren Feld Abstand.
Zeichne mit deiner Toolbox nebeneinander Quadrate der Grösse 2, 3, 4 und 5, wieder mit je einem leeren Feld Abstand. Verwende eine Schleife und eine Variable size.
Schreibe eine Funktion grade(points), welche die Note für eine Punktzahl zurückgibt:
$$\text{Note} = \frac{5 \cdot \text{Punkte}}{\text{Punkte für Note 6}} + 1$$
POINTS_FOR_SIX = 40.round(note, 2) auf zwei Nachkommastellen.
Schreibe eine Funktion fortune_cookie(), die bei jedem Aufruf zufällig einen von fünf Glückskeks-Sprüchen zurückgibt. Erfinde selbst Sprüche oder klaue sie dreist aus dem Internet. Rufe die Funktion im Hauptprogramm dreimal auf und gib die Sprüche aus.
Eine quadratische Gleichung hat die Form $ax^2 + bx + c = 0$ (mit $a \neq 0$). Ihre Lösungen sind:
$$x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}$$
Wie viele Lösungen es gibt, verrät die Diskriminante $D = b^2 - 4ac$: Ist $D < 0$, gibt es keine Lösung, ist $D = 0$, gibt es eine, und ist $D > 0$, gibt es zwei.
count_solutions(a, b, c), die die Anzahl Lösungen zurückgibt (0, 1 oder 2).print_solutions(a, b, c), die count_solutions verwendet und die Lösungen in einem schönen Satz ausgibt.Kontrolle: - $3x^2 - 6x - 5 = 0$ hat zwei Lösungen: $-0.632993$ und $2.63299$ - $x^2 - 4x + 4 = 0$ hat eine Lösung: $2$ - $x^2 + 2x + 7 = 0$ hat keine Lösung
def name(parameter1, parameter2=vorgabewert): # Definition # Funktionskörper (eingerückt) return resultat # optional: beendet die Funktion wert = name(argument1, argument2) # Aufruf
def) merkt sich den Code, erst der Aufruf (mit Klammern!) führt ihn aus. Definitionen stehen oben.return gibt einen Wert an den Aufrufer zurück und beendet die Funktion. print zeigt nur etwas an.assert testen.Mit Funktionen kannst du nun auch grössere Probleme in kleine Teile zerlegen. Genau das brauchst du in Algorithmen II!