Kapitel V - Funktionen
Innerhalb der vorherigen Kapitel haben wir bereits eine Vielzahl von Programmen geschrieben. Teilweise waren die Anweisungen zum Zeichnen von (komplexeren) Bildern bzw. Figuren umfangreich. Wir haben durch die Kontrollstruktur Schleife, bzw. genauer einer Zählschleife, eine Möglichkeit erhalten, die Programme zu verkürzen.
Wir werden nun eine weitere Programmiertechnik kennenlernen - die unterschiedliche Vorteile bietet - mit der man Programme weiter kürzen kann bzw. aus zusammenhängenden Einzelstücken zusammenfassen kann. Ebenfalls wird es möglich, eine Gruppe von Anweisungen an unterschiedlichen Stellen wieder zu verwenden. Dazu vergibt man für einen Teil von Anweisungen einen Namen, so dass man diese Anweisungen durch Ansprechen des entsprechenden Namens aufrufen bzw. ausführen lassen kann.
Nach erfolgreicher Bearbeitung dieses Kapitels kannst du
- eine Folge von Anweisungen zu einer Funktion zusammenfassen.
- das reservierte Wort def zielgerichtet einsetzen.
- zwischen Funktionskopf und -körper unterscheiden und beide erläutern.
- Funktionen in deinen Programmen verwenden.
- Docstrings zur Dokumentation von Funktionen verwenden.
- zwischen Funktionsaufruf und -definition unterscheiden.
- die Vorteile der Verwendung von Funktionen benennen.
Fertige eine Skizze der "Ausgabe" des folgenden Programmes an:
# # Autor(en)# Christian Graf# # Datum# 19.03.2011# # letzte Aenderung# 19.03.2011# # Beschreibung# ???from turtle import TurtlemyTurtle = Turtle("turtle")seitenlaenge = 20abstand = 2*seitenlaengefor i in range(10):myTurtle.forward(seitenlaenge)myTurtle.left(120)myTurtle.forward(seitenlaenge)myTurtle.left(120)myTurtle.forward(seitenlaenge)myTurtle.left(120)myTurtle.forward(abstand)# Fenster geoeffnet lassen - fuer LinuxmyTurtle.screen._root.mainloop()
Als generelles Vorgehen bei solchen Aufgaben bietet es sich an, die Schleife insgesamt erstmal zu ignorieren und sich nur den Schleifenkörper anzuschauen. Entsprechend stellen die nächsten Ausführungen nur eine Wiederholung dar. Zu beachten ist allerdings, dass sich dabei der Startpunkt ändert.
Wir wollen das vorliegende Programm nun schrittweise erweitern und verbessern. Im Rahmen der Erweiterung wollen wir verhindern, dass zwischen den einzelnen Dreiecken - durch die Bewegung der Turtle - ein Strich gezeichnet wird. Dazu stellt die Turtle eine Fähigkeit (Methode) bereit. Der Aufruf der Methoden penup() bzw. pendown() bei einem existierenden Turtle-Objekt sorgt dafür, dass die entsprechende Turtle ihren Stift hochnimmt oder wieder absetzt.
- Überlege dir, für welche Anweisung / Bewegung das Turtle-Objekt seinen Stift anheben muss.
- Ergänze die Anweisungen, so dass die Striche bei der Forward-Bewegung zwischen den Dreiecken nicht mehr gezeichnet werden.
- In welche beiden "Phasen" kann man den Schleifenkörper einteilen?
- Erläutere, ob du noch eine Möglichkeit zur Optimierung des Programmes siehst.
-
Im Zuge dieser Anweisung zeichnet das Turtle-Objekt den "Platzhalter" zwischen den beiden Dreiecken. Dafür muss der Stift angehoben werden, damit die entsprechende Linie nicht gezeichnet wird.myTurtle.forward(abstand) -
Vor der entsprechenden Vorwärtsbewegung muss der Stift angehoben werden und danach wieder abgelassen werden. Achte auf die übliche Syntax des Methodenaufrufes und die Angabe des Objektes, das die Aktion ausführen soll.# # Autor(en)# Christian Graf# # Datum# 19.03.2011# # letzte Aenderung# 19.03.2011# # Beschreibung# ???from turtle import TurtlemyTurtle = Turtle("turtle")seitenlaenge = 20abstand = 2*seitenlaengefor i in range(10):myTurtle.forward(seitenlaenge)myTurtle.left(120)myTurtle.forward(seitenlaenge)myTurtle.left(120)myTurtle.forward(seitenlaenge)myTurtle.left(120)myTurtle.penup()myTurtle.forward(abstand)myTurtle.pendown()# Fenster geoeffnet lassen - fuer LinuxmyTurtle.screen._root.mainloop() - Der Schleifenkörper kann in das Zeichnen eines Dreieckes und das Zeichnen des Platzhalters unterteilt werden.
- Da das Zeichnen des Dreieckes aus einer Wiederholung von (gleichen) Vorwärtsbewegungen und Drehungen besteht, könnte man dieses ebenfalls durch eine Schleife realisieren.
Fasse das Zeichnen eines einzelnen Dreieckes ebenfalls in einer Schleife zusammen. Achte darauf, dass du hier entsprechend den zugehörigen Schleifenkörper zweimal einrücken musst.
Hinweis: Es kann sinnvoll sein, wenn du dir das Ende einer jeweiligen Schleife - insbesondere bei einer Verschachtelung von Schleifen - mit einem Kommentar markierst. Dadurch wird die notwendige doppelte Einrückung ggf. transparenter für dich.# # Autor(en)# Christian Graf# # Datum# 19.03.2011# # letzte Aenderung# 19.03.2011# # Beschreibung# ???from turtle import TurtlemyTurtle = Turtle("turtle")seitenlaenge = 20abstand = 2*seitenlaengefor i in range(10):for j in range(3):myTurtle.forward(seitenlaenge)myTurtle.left(120)# Ende der Dreiecks-SchleifemyTurtle.penup()myTurtle.forward(abstand)myTurtle.pendown()# Ende der Wiederholungs-Schleife# Fenster geoeffnet lassen - fuer LinuxmyTurtle.screen._root.mainloop()
Wie du - vermutlich - bereits festgestellt hast, ist das Programm nun gar nicht mehr so einfach zu lesen. Dennoch ist und bleibt das Programm vom Aufbau her einfach: Es wird zehn mal ein Dreieck und ein Abstandshalter gezeichnet. Dies setzt aber voraus, dass wir den Aufbau und die Funktionsweise des Programmes analysiert und verstanden haben. Wir werden nun die angesprochene Programmiertechnik kennenlernen, die das Programm leichter lesbar macht. Diese Programmiertechnik erlaubt uns aber noch viel mehr:
- Einfache Wiederverwendung einmal programmierter Programmteile.
- Zusammenfassung einzelner Anweisungen zu einem "Ganzen".
- Erstellung eines wichtigen Bestandteiles eines ganzen Programmes.
- Es wird möglich, ein Programm in einzelne "Teile" zu zerlegen und später einfach weitere Funktionalitäten zu ergänzen.
- Logische oder syntaktische Fehler müssen nur noch an einer Stelle korrigiert werden.
- Der Einsatz dieser Technik ist nötig, um auf Ereignisse zu reagieren. Zu Ereignissen zählen beispielsweise "Der Benutzer drückt die Taste 'a'" oder "Die linke Maustaste wurde gedrückt.".
Im Rahmen dieser Programmiertechnik werden wir eine Folge von Anweisungen zu einer sogenannten Funktion zusammenfassen. Diese Funktion können wir später immer wieder in unserem Programm verwenden. Wir schreiben statt der Folge von Anweisungen, die wir benötigen, einfach den entsprechenden Funktionsaufruf. Wie das genau funktioniert, werden wir gleich sehen.
Die entsprechende Programmiertechnik, ein Programm in einzelne kleine Unterprogramme zu zerlegen, gibt es so gut wie in allen Programmiersprachen. In Python wird dies durch Funktionen (und Methoden) realisiert. In anderen Programmiersprachen findet eine stärkere Unterscheidung zwischen Methoden, Funktionen und sog. Prozeduren statt.
Da man in Python im Prinzip aber nur Funktionen definieren kann und daher eine genaue Unterscheidung zwischen Funktionen und Prozeduren nicht möglich ist, wollen wir uns nun nicht näher mit einer genauen Unterscheidung auseinandersetzen.
Definition von Funktionen
Bei der Definition von Funktionen unterscheidet man, wie bei einer Schleife, zwischen dem Funktionskopf und dem Funktionskörper. Auch hier gilt wieder: Alle Anweisungen, die zum Funktionskörper gehören, müssen einen Schritt weiter eingerückt sein. In den meisten Fällen übernimmt die Entwicklungsumgebung die Einrückung für uns, wenn sie den Funktionskopf erkannt hat. Auch diese Anweisungen bilden einen Block.
Auch hier werden die eingerückten Anweisungen - wie bei den Schleifen auch - wieder als Anweisungsblock bezeichnet.
Der Funktionskopf beginnt mit dem Schlüsselwort def gefolgt von einem gültigen Funktionsnamen. Hier gelten die gleichen Spielregeln wie für die Wahl von Variablen. Allerdings mit einer wichtigen Erweiterung: Das Wort def gehört auch zur Liste der reservierten Wörter und darf nicht verwendet werden. Auf den Funktionsnamen folgen zwei Klammern () und ein Doppelpunkt :. Dieser leitet wie gehabt den zugehörigen Anweisungsblock ein.
Muster:
# Kopf der Funktiondef myFunction():# Beginn Funktionskörper# ... (Anweisungsblock)# Letzte Zeile des Funktionskörpers# Diese Zeile gehört nicht mehr zum Funktionskörper (Anweisungsblock in beendet)
Beispiel:
# Kopf der Funktiondef halloFunktion():print("Hallo, ich bin eine Funktion.")print("Auf Wiedersehen, ich gehöre nicht zu einer Funktion.")
Kopiere das obige Beispiel und füge es in einer neuen Datei ein. Ergänze es zu einem ganzen Programm, speichere dieses und starte das Programm. Kannst du die Ausgabe erklären?
Damit es sich um ein ganzes Programm handelt, müssen wir noch einen Kopfkommentar ergänzen. Als Ausgabe erhalten wir:
Auf Wiedersehen, ich gehöre nicht zu einer Funktion.
Obwohl wir zwei print()-Anweisungen in unserem Programm vorliegen haben, wird aber scheinbar nur eine davon ausgeführt. Das ist jene Anweisung, die nicht innerhalb des Funktionskörpers steht.
Nach der Ausführung der Zeilen
def halloFunktion():print("Hallo, ich bin eine Funktion")
ist scheinbar nichts geschehen, ansonsten hätten wir in der letzten Aufgabe eine entsprechende Ausgabe erhalten müssen, was aber offensichtlich nicht erfolgt ist.
In Wahrheit ist intern aber dennoch etwas passiert. Das reservierte Wort def hat Python mit der neuen Funktion halloFunktion() bekannt gemacht. Python kennt diese nun und weiß, was passieren soll, wenn wir diese Funktion aufrufen. Dazu müssen wir nun in der Shell die Anweisung
halloFunktion() ausführen und erhalten die Ausgabe
Hallo, ich bin eine Funktion.
- Eine Funktionsdefinition leitet Python dazu an, zu lernen, welche Befehle bei Aufruf der Funktion ausgeführt werden sollen. Diese Befehle werden aber zu diesem Zeitpunkt noch nicht ausgeführt.
- Wollen wir, dass Python die entsprechenden Befehle ausführt, so müssen wir den Funktionsnamen gefolgt von Klammern () aufrufen. Dies geht allerdings erst, nachdem Python mit der Funktion bekannt gemacht worden ist! Wenn wir die Klammern vergessen, wird keine Funktion aufgerufen. Ggf. wird dann in der Shell eine ähnlich kryptische Meldung ausgegeben, wie wir sie von der Erzeugung eines Turtle-Objektes kennen.
Kommentierung von Funktionen
Umfangreiche Python-Programme setzen sich in großen Teilen aus dem Aufruf einzelner Funktionen zusammen. Diese müssen entsprechend vorher definiert werden. Um allerdings den Überblick zu behalten, ist es zum einen wichtig, sinnvolle Namen zu vergeben. Zum anderen können Kommentare helfen, den Überblick zu behalten. In Rahmen der Funktionsdefinition gibt es besondere Kommentare, sogenannte Docstrings.
Wie in der Abbildung zu sehen, werden die Docstrings als eine Art Pop-Up angezeigt, wenn wir die Funktion bei der Programmierung benutzen wollen. Wir können uns also auf diese Weise nochmal versichern, ob die Funktion genau die Funktionalität bereitstellt, die wir an der Stelle des Programmes benötigen.
Du hast diese Zusatzinformation sicherlich bereits bei einigen Methodenaufrufen gesehen.
Muster zum Anlegen eines Docstrings:
# Kopf der Funktiondef myFunction():""" Hier steht der Docstring """# Beginn Funktionskörper# ...# Letzte Zeile des Funktionskörpers# Diese Zeile gehört nicht mehr zum Funktionskörper
Der Docstring wird direkt unter dem Funktionskopf angelegt. Eine richtige Einrückung ist auch hier zwingend erforderlich! Der Docstring beginnt nicht, wie bei Kommentaren üblich mit dem Zeichen #, sondern beginnt und endet mit jeweils drei Anführungszeichen """. Die Verwendung von Anführungszeichen zeigt die Parallele im Namen zu den Zeichenketten, den sog. Strings. Entsprechend erfolgt eine Einfärbung innerhalb des PyScripters in der Farbe eines Strings.
Ändere das Beispielprogramm mit unserer halloFunktion() so ab, dass
- die Funktion einen sinnvollen Docstring enthält.
- im Kopfkommentar eine sinnvolle Beschreibung zu finden ist.
- die Funktion halloFunktion() vor der bereits integrierten Ausgabe aufgerufen wird.
# # Autor(en) Christian Graf# # Datum 19.03.2011# # Beschreibung Beispiel zum Umgang mit und Definition von Funktionen# Kopf der Funktiondef halloFunktion():""" Docstring: Gibt aus, dass es sich um eine Funktion handelt """print("Hallo, ich bin eine Funktion.")# Ende der Funktion# Aufruf der FunktionhalloFunktion()print("Auf Wiedersehen, ich gehöre nicht zu einer Funktion.")
Zurück zu unserer Folge von Dreiecken
In einer der vorherigen Aufgaben haben wir uns überlegt, dass sich der Schleifenkörper des anfänglichen Programmes in zwei Teile bzw. Phasen einteilen lässt. Einmal ist dies das Zeichnen eines Dreieckes und einmal das Zeichnen eines Abstandshalters. Für diese Teile wollen wir jetzt entsprechende Funktionen definieren.
Definiere jeweils eine Funktion, die entsprechend den obigen Vorgaben
- ein Dreieck zeichnet.
- einen Abstandshalter zeichnet.
-
Du musst die Zeichnung des Dreieckes nicht mittels einer Schleife realisieren.def dreieck():""" Zeichnet ein (gleichseitiges) Dreieck mit der Seitenlänge seitenlaenge """for j in range(3):myTurtle.forward(seitenlaenge)myTurtle.left(120) -
def abstandshalter():""" Zeichnet einen Abstandshalter zwischen den einzelnen Dreiecken """myTurtle.penup()myTurtle.forward(abstand)myTurtle.pendown()
- eine entsprechende Einrückung. Stellenweise muss diese zweistufig sein, da wir innerhalb der Funktion eine Schleife verwenden wollen.
- Einhaltung der Syntax zur Definition einer Funktion.
- Angabe eines Docstrings.
- die vorherige Belegung der Variablen mit Werten bzw. Objekten. Dies muss in unserem Fall innerhalb des eigentlichen Programmes VOR der Definition der Funktionen erfolgen. Hier ist das die Erstellung eines Turtle-Objektes und die Festlegung der Seitenlänge bzw. des Abstandes.
Ergänze die beiden Funktionen in unser anfängliches Programm und nutze diese zur Anfertigung der Ausgabe.
Achte auf die richtige Reihenfolge: Du kannst Funktionen erst verwenden, wenn Python mit diesen bekannt gemacht worden ist.
Alternative:# # Autor(en): Christian Graf# # Datum: 04.03.2011# # Beschreibung: Zeichnen vieler Dreiecke mittels zweier Funktionen und einer Schleifefrom turtle import Turtle# Belegung der VariablenmyTurtle = Turtle("turtle")seitenlaenge = 20abstand = 2*seitenlaenge# Definition der Funktionen# NACH Belegung der Variablendef dreieck ():myTurtle.forward(seitenlaenge)myTurtle.left(120)myTurtle.forward(seitenlaenge)myTurtle.left(120)myTurtle.forward(seitenlaenge)myTurtle.left(120)def abstandshalter():myTurtle.penup()myTurtle.forward(abstand)myTurtle.pendown()# eigentliches Programmfor i in range(10):# Aufruf der Funktion zur Zeichnung eines Dreieckesdreieck()# Aufruf der Funktion zur Zeichnung eines Abstandshaltersabstandshalter()# Fenster geoeffnet lassen - fuer LinuxmyTurtle.screen._root.mainloop()
Quizfrage: Gib an, was in den obigen Programmen vergessen wurde!# # Autor(en): Christian Graf# # Datum: 04.03.2011# # Beschreibung: Zeichnen vieler Dreiecke mittels zweier Funktionen und zwei Schleifenfrom turtle import Turtle# Belegung der VariablenmyTurtle = Turtle("turtle")seitenlaenge = 20abstand = 2*seitenlaenge# Definition der Funktionen# NACH Belegung der Variablendef dreieck ():for j in range(3):myTurtle.forward(seitenlaenge)myTurtle.left(120)def abstandshalter():myTurtle.penup()myTurtle.forward(abstand)myTurtle.pendown()# eigentliches Programmfor i in range(10):# Aufruf der Funktion zur Zeichnung eines Dreieckesdreieck()# Aufruf der Funktion zur Zeichnung eines Abstandshaltersabstandshalter()# Fenster geoeffnet lassen - fuer LinuxmyTurtle.screen._root.mainloop()
Zur Übung: Ergänzung um eine Funktion zum Zeichnen eines Quadrates
Ergänze das obige Programm um eine Funktion zur Zeichnung eines Quadrates. Nutze innerhalb der Funktion eine Schleife und ändere das Programm so ab, dass eine Folge von Quadraten gezeichnet wird.
# # Autor(en): Christian Graf# # Datum: 04.03.2011# # Beschreibung: Zeichnen vieler Quadrate mittels zweier Funktionen und zwei Schleifenfrom turtle import Turtle# Belegung der VariablenmyTurtle = Turtle("turtle")seitenlaenge = 20abstand = 2*seitenlaenge# Definition der Funktionen# NACH Belegung der Variablendef dreieck ():""" Zeichnet ein Dreieck mit der festgelegten Seitenlaenge """for j in range(3):myTurtle.forward(seitenlaenge)myTurtle.left(120)def quadrat():""" Zeichnet ein Quadrat mit der festgelegten Seitenlaenge """for j in range(4):myTurtle.forward(seitenlaenge)myTurtle.left(360/4)def abstandshalter():""" Zeichnet einen Abstandshalter zwischen zwei Figuren """myTurtle.penup()myTurtle.forward(abstand)myTurtle.pendown()# eigentliches Programmfor i in range(10):# Aufruf der Funktion zur Zeichnung eines Quadratesquadrat()# Aufruf der Funktion zur Zeichnung eines Abstandshaltersabstandshalter()# Fenster geoeffnet lassen - fuer LinuxmyTurtle.screen._root.mainloop()
Durch eine Unterteilung in einzelne Funktionen und die zugehörigen Docstrings wird unser Programm leichter lesbar, verständlicher und übersichtlicher. Ebenfalls wird es möglich sein, die drei Funktionen an anderen Stellen wieder zu verwenden.
In der folgenden Abbildung sieht du ein Muster, das du für die Konstruktion von weiteren Übungsaufgaben verwenden kannst. Versuche doch einmal das Zeichnen von zwei Quadraten zu einer Funktionen zusammenzufassen, die die bereits definierte Funktion nutzt. Nun kannst du ganz leicht das dargestellte Muster zeichnen lassen. Viel Erfolg!
Weiter geht's mit der Lernfortschrittskontrolle zu Kapitel V.