Kapitel 4. Funktionen und Schnittstellen
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
In diesem Kapitel wird ein Modul namens jupyturtle
vorgestellt, mit dem du einfache Zeichnungen erstellen kannst, indem du einer imaginären Schildkröte Anweisungen gibst. Wir werden dieses Modul verwenden, um Funktionen zu schreiben, die Quadrate, Polygone und Kreise zeichnen - und um das Design von Schnittstellen zu demonstrieren, d.h. eine Art, Funktionen zu entwerfen, die zusammenarbeiten.
Das Modul jupyturtle
Um das Modul jupyturtle
zu verwenden, können wir es wie folgt importieren:
import
jupyturtle
Jetzt können wir die Funktionen verwenden, die im Modul definiert sind, wie make_turtle
und forward
:
jupyturtle
.
make_turtle
()
jupyturtle
.
forward
(
100
)
make_turtle
erstellt eine Leinwand, d.h. eine Fläche auf dem Bildschirm, auf der wir zeichnen können, und eine Schildkröte, die durch einen kreisförmigen Panzer und einen dreieckigen Kopf dargestellt wird. Der Kreis zeigt die Position der Schildkröte an und das Dreieck gibt die Richtung an, in die sie schaut.
forward
bewegt die Schildkröte um eine bestimmte Strecke in die Richtung, in die sie blickt, und zeichnet dabei ein Liniensegment entlang der Strecke. Die Entfernung wird in willkürlichen Einheiten angegeben - die tatsächliche Größe hängt vom Bildschirm deines Computers ab.
Wir werden Funktionen, die im Modul jupyturtle
definiert sind, sehr oft verwenden, daher wäre es schön, wenn wir nicht jedes Mal den Namen des Moduls schreiben müssten. Das ist möglich, wenn wir das Modul wie folgt importieren:
from
jupyturtle
import
make_turtle
,
forward
Diese Version der Import-Anweisung importiert make_turtle
und forward
aus dem Modul jupyturtle
, sodass wir sie wie folgt aufrufen können:
make_turtle
()
forward
(
100
)
jupyturtle
bietet zwei weitere Funktionen an, die wir verwenden werden: left
und right
. Wir importieren sie wie folgt:
from
jupyturtle
import
left
,
right
left
veranlasst die Schildkröte, sich nach links zu drehen. Sie benötigt ein Argument, das den Winkel der Drehung in Grad angibt. Wir können zum Beispiel eine 90-Grad-Linkskurve wie folgt machen:
make_turtle
()
forward
(
50
)
left
(
90
)
forward
(
50
)
Dieses Programm bewegt die Schildkröte nach Osten und dann nach Norden und lässt dabei zwei Liniensegmente zurück. Bevor du weitermachst, schau, ob du das Programm so abändern kannst, dass es ein Quadrat ergibt.
Ein Quadrat machen
Hier ist eine Möglichkeit, ein Quadrat zu machen:
make_turtle
()
forward
(
50
)
left
(
90
)
forward
(
50
)
left
(
90
)
forward
(
50
)
left
(
90
)
forward
(
50
)
left
(
90
)
Da dieses Programm dasselbe Zeilenpaar viermal wiederholt, können wir das Gleiche mit einer for
Schleife prägnanter machen:
make_turtle
()
for
i
in
range
(
4
):
forward
(
50
)
left
(
90
)
Verkapselung und Verallgemeinerung
Wir nehmen den Code für das Zeichnen von Quadraten aus dem vorherigen Abschnitt und fügen ihn in eine Funktion namens square
ein:
def
square
():
for
i
in
range
(
4
):
forward
(
50
)
left
(
90
)
Jetzt können wir die Funktion wie folgt aufrufen:
make_turtle
()
square
()
Einen Teil des Codes in eine Funktion zu verpacken, nennt man Kapselung. Einer der Vorteile der Kapselung ist, dass der Code mit einem Namen versehen wird, der als eine Art Dokumentation dient. Ein weiterer Vorteil ist, dass es bei der Wiederverwendung des Codes übersichtlicher ist, eine Funktion zweimal aufzurufen, als den Body zu kopieren und einzufügen!
In der aktuellen Version ist die Größe des Quadrats immer 50
. Wenn wir Quadrate mit unterschiedlichen Größen zeichnen wollen, können wir die Länge der Seiten als Parameter nehmen:
def
square
(
length
):
for
i
in
range
(
4
):
forward
(
length
)
left
(
90
)
Jetzt können wir Quadrate mit unterschiedlichen Größen zeichnen:
make_turtle
()
square
(
30
)
square
(
60
)
Das Hinzufügen eines Parameters zu einer Funktion wird als Verallgemeinerung bezeichnet, weil es die Funktion allgemeiner macht: Bei der vorherigen Version ist das Quadrat immer gleich groß; bei dieser Version kann es beliebig groß sein.
Wenn wir einen weiteren Parameter hinzufügen, können wir sie noch allgemeiner gestalten. Die folgende Funktion zeichnet regelmäßige Polygone mit einer bestimmten Anzahl von Seiten:
def
polygon
(
n
,
length
):
angle
=
360
/
n
for
i
in
range
(
n
):
forward
(
length
)
left
(
angle
)
In einem regelmäßigen Vieleck mit n
Seiten beträgt der Winkel zwischen benachbarten Seiten 360 / n
Grad.
Das folgende Beispiel zeichnet ein 7
-seitiges Polygon mit einer Seitenlänge von 30
:
make_turtle
()
polygon
(
7
,
30
)
Wenn eine Funktion mehr als ein paar numerische Argumente hat, kann man leicht vergessen, welche das sind oder in welcher Reihenfolge sie stehen sollten. Es kann eine gute Idee sein, die Namen der Parameter in die Argumentenliste aufzunehmen:
make_turtle
()
polygon
(
n
=
7
,
length
=
30
)
Diese werden manchmal "benannte Argumente" genannt, weil sie die Parameternamen enthalten. In Python werden sie jedoch häufiger als Schlüsselwortargumente bezeichnet (nicht zu verwechseln mit Python-Schlüsselwörtern wie for
und def
).
Die Verwendung des Zuweisungsoperators =
erinnert dich daran, wie Argumente und Parameter funktionieren - wenn du eine Funktion aufrufst, werden die Argumente den Parametern zugewiesen.
Annäherung an einen Kreis
Nehmen wir nun an, wir wollen einen Kreis zeichnen. Das können wir annähernd erreichen, indem wir ein Polygon mit vielen Seiten zeichnen, so dass jede Seite so klein ist, dass sie schwer zu sehen ist. Hier ist eine Funktion, die polygon
verwendet, um ein 30-seitiges Polygon zu zeichnen, das einen Kreis annähernd darstellt:
import
math
def
circle
(
radius
):
circumference
=
2
*
math
.
pi
*
radius
n
=
30
length
=
circumference
/
n
polygon
(
n
,
length
)
circle
nimmt den Radius des Kreises als Parameter an. Sie berechnet circumference
, das ist der Umfang eines Kreises mit dem angegebenen Radius. n
ist die Anzahl der Seiten, also ist circumference / n
die Länge jeder Seite.
Die Ausführung dieser Funktion kann sehr lange dauern. Wir können sie beschleunigen, indem wir make_turtle
mit einem Schlüsselwortargument namens delay
aufrufen, das die Zeit in Sekunden angibt, die die Schildkröte nach jedem Schritt wartet. Der Standardwert ist 0.2
Sekunden - wenn wir ihn auf 0.02
setzen, läuft die Funktion etwa 10 Mal schneller.
make_turtle
(
delay
=
0.02
)
circle
(
30
)
Eine Einschränkung dieser Lösung ist, dass n
eine Konstante ist, was bedeutet, dass bei sehr großen Kreisen die Seiten zu lang sind und bei kleinen Kreisen verschwenden wir Zeit damit, sehr kurze Seiten zu zeichnen. Eine Möglichkeit ist, die Funktion zu verallgemeinern, indem du n
als Parameter nimmst. Aber wir wollen es erst einmal einfach halten.
Refactoring
Schreiben wir nun eine allgemeinere Version von circle
, genannt arc
, die einen zweiten Parameter, angle
, annimmt und einen Kreisbogen zeichnet, der den angegebenen Winkel umspannt. Wenn angle
zum Beispiel 360
Grad ist, wird ein kompletter Kreis gezeichnet. Wenn angle
180
Grad ist, wird ein Halbkreis gezeichnet.
Um circle
zu schreiben, konnten wir polygon
wiederverwenden, weil ein vielseitiges Polygon eine gute Annäherung an einen Kreis ist. Aber wir können polygon
nicht verwenden, um arc
zu schreiben.
Stattdessen erstellen wir die allgemeinere Version von polygon
, genannt polyline
:
def
polyline
(
n
,
length
,
angle
):
for
i
in
range
(
n
):
forward
(
length
)
left
(
angle
)
polyline
benötigt als Parameter die Anzahl der zu zeichnenden Liniensegmente, n
, die Länge der Segmente, length
, und den Winkel zwischen ihnen, angle
.
Jetzt können wir polygon
umschreiben und polyline
verwenden:
def
polygon
(
n
,
length
):
angle
=
360.0
/
n
polyline
(
n
,
length
,
angle
)
Und wir können polyline
verwenden, um arc
zu schreiben:
def
arc
(
radius
,
angle
):
arc_length
=
2
*
math
.
pi
*
radius
*
angle
/
360
n
=
30
length
=
arc_length
/
n
step_angle
=
angle
/
n
polyline
(
n
,
length
,
step_angle
)
arc
ist ähnlich wie circle
, nur dass es arc_length
berechnet, was ein Bruchteil des Kreisumfangs ist.
Schließlich können wir circle
umschreiben und arc
verwenden:
def
circle
(
radius
):
arc
(
radius
,
360
)
Um zu überprüfen, ob diese Funktionen wie erwartet funktionieren, verwenden wir sie, um etwas wie eine Schnecke zu zeichnen. Mit delay=0
läuft die Schildkröte so schnell wie möglich.
make_turtle
(
delay
=
0
)
polygon
(
n
=
20
,
length
=
9
)
arc
(
radius
=
70
,
angle
=
70
)
circle
(
radius
=
10
)
In diesem Beispiel haben wir mit funktionierendem Code begonnen und ihn mit verschiedenen Funktionen umstrukturiert. Änderungen wie diese, die den Code verbessern, ohne sein Verhalten zu ändern, nennt man Refactoring.
Wenn wir vorausgeplant hätten, hätten wir vielleicht zuerst polyline
geschrieben und das Refactoring vermieden, aber oft weiß man zu Beginn eines Projekts nicht genug, um alle Funktionen zu entwerfen. Sobald du mit dem Programmieren beginnst, verstehst du das Problem besser. Manchmal ist das Refactoring ein Zeichen dafür, dass du etwas gelernt hast.
Stapel-Diagramm
Wenn wir circle
aufrufen, ruft es arc
auf, das polyline
aufruft. Wir können ein Stapeldiagramm verwenden, um diese Abfolge der Funktionsaufrufe und die Parameter für jeden Aufruf darzustellen:
Beachte, dass der Wert von angle
in polyline
ein anderer ist als der Wert von angle
in arc
. Parameter sind lokal, d.h. du kannst denselben Parameternamen in verschiedenen Funktionen verwenden; er ist in jeder Funktion eine andere Variable und kann sich auf einen anderen Wert beziehen.
Ein Entwicklungsplan
Ein Entwicklungsplan ist ein Prozess zum Schreiben von Programmen. Der Prozess, den wir in diesem Kapitel verwendet haben, heißt "Kapselung und Verallgemeinerung". Die Schritte dieses Prozesses sind:
-
Beginne damit, ein kleines Programm ohne Funktionsdefinitionen zu schreiben.
-
Sobald du das Programm zum Laufen gebracht hast, identifiziere einen zusammenhängenden Teil davon, kapsle den Teil in einer Funktion und gib ihr einen Namen. Kopiere den funktionierenden Code und füge ihn ein, damit du ihn nicht erneut abtippen (und debuggen) musst.
-
Verallgemeinere die Funktion, indem du geeignete Parameter hinzufügst.
-
Wiederhole die Schritte 1 bis 3, bis du eine Reihe von funktionierenden Funktionen hast.
-
Suche nach Möglichkeiten, das Programm durch Refactoring zu verbessern. Wenn du zum Beispiel an mehreren Stellen ähnlichen Code hast, solltest du in Erwägung ziehen, ihn in eine entsprechend allgemeine Funktion zu integrieren.
Dieses Verfahren hat einige Nachteile - wir werden später Alternativen sehen - aber es kann nützlich sein, wenn du im Voraus nicht weißt, wie du das Programm in Funktionen unterteilen sollst. Mit diesem Ansatz kannst du während der Arbeit entwerfen.
Der Entwurf einer Funktion besteht aus zwei Teilen:
- Schnittstelle
-
Wie die Funktion verwendet wird, einschließlich ihres Namens, der Parameter, die sie benötigt, und was die Funktion tun soll
- Umsetzung
Hier ist zum Beispiel die erste Version von circle
, die wir geschrieben haben und die polygon
verwendet:
def
circle
(
radius
):
circumference
=
2
*
math
.
pi
*
radius
n
=
30
length
=
circumference
/
n
polygon
(
n
,
length
)
Und hier ist die überarbeitete Version, die arc
verwendet:
def
circle
(
radius
):
arc
(
radius
,
360
)
Diese beiden Funktionen haben die gleiche Schnittstelle - sie nehmen die gleichen Parameter und tun das Gleiche - aber sie haben unterschiedliche Implementierungen.
Docstrings
Ein docstring ist eine Zeichenkette am Anfang einer Funktion, die die Schnittstelle erklärt ("doc" ist die Abkürzung für "Dokumentation"). Hier ist ein Beispiel:
def
polyline
(
n
,
length
,
angle
):
"""Draws line segments with the given length and angle between them.
n: integer number of line segments
length: length of the line segments
angle: angle between segments (in degrees)
"""
for
i
in
range
(
n
):
forward
(
length
)
left
(
angle
)
Gemäß der Konvention von handelt es sich bei docstrings um Strings in dreifachen Anführungszeichen, die auch als mehrzeilige Strings bezeichnet werden, weil die dreifachen Anführungszeichen es ermöglichen, dass sich die Zeichenkette über mehr als eine Zeile erstreckt.
Ein Docstring sollte:
-
Erkläre kurz und bündig, was die Funktion tut, ohne dabei ins Detail zu gehen,
-
Erkläre, welche Auswirkungen die einzelnen Parameter auf das Verhalten der Funktion haben, und
-
Gib an, welcher Typ die einzelnen Parameter sein sollten, wenn es nicht offensichtlich ist.
Das Schreiben dieser Art von Dokumentation ist ein wichtiger Teil der Schnittstellengestaltung. Eine gut gestaltete Oberfläche sollte einfach zu erklären sein. Wenn es dir schwerfällt, eine deiner Funktionen zu erklären, könnte die Oberfläche vielleicht verbessert werden.
Fehlersuche
Eine Schnittstelle ist wie ein Vertrag zwischen einer Funktion und einem Aufrufer. Der Aufrufer verpflichtet sich, bestimmte Argumente bereitzustellen, und die Funktion verpflichtet sich, bestimmte Aufgaben zu übernehmen.
Zum Beispiel benötigt polyline
drei Argumente: n
muss eine ganze Zahl sein, length
sollte eine positive Zahl sein und angle
muss eine Zahl sein, die in Grad angegeben wird.
Diese Bedingungen werden als Vorbedingungen bezeichnet, weil sie wahr sein müssen, bevor die Funktion ausgeführt wird. Umgekehrt sind die Bedingungen am Ende der Funktion Nachbedingungen. Zu den Nachbedingungen gehören die beabsichtigte Wirkung der Funktion (wie das Zeichnen von Liniensegmenten) und alle Nebeneffekte (wie das Bewegen der Schildkröte oder andere Änderungen).
Vorbedingungen liegen in der Verantwortung des Aufrufers. Wenn der Aufrufer gegen eine Vorbedingung verstößt und die Funktion nicht richtig funktioniert, liegt der Fehler beim Aufrufer, nicht bei der Funktion.
Wenn die Vorbedingungen erfüllt sind und die Nachbedingungen nicht, liegt der Fehler in der Funktion. Wenn deine Vor- und Nachbedingungen klar sind, können sie bei der Fehlersuche helfen.
Glossar
Schnittstellengestaltung: Ein Prozess zur Gestaltung der Schnittstelle einer Funktion, der auch die Parameter umfasst, die sie annehmen soll.
Leinwand: Ein Fenster, in dem grafische Elemente wie Linien, Kreise, Rechtecke und andere Formen angezeigt werden.
Einkapselung: Der Prozess der Umwandlung einer Folge von Anweisungen in eine Funktionsdefinition.
Verallgemeinerung: Der Prozess, bei dem etwas unnötig Spezifisches (wie eine Zahl) durch etwas angemessen Allgemeines (wie eine Variable oder einen Parameter) ersetzt wird.
Schlüsselwort-Argument: Ein Argument, das den Namen des Parameters enthält.
Refactoring: Der Prozess, bei dem ein funktionierendes Programm geändert wird, um Funktionsschnittstellen und andere Eigenschaften des Codes zu verbessern.
Entwicklungsplan: Ein Prozess zum Schreiben von Programmen.
docstring: Eine Zeichenfolge, die am Anfang einer Funktionsdefinition erscheint, um die Schnittstelle der Funktion zu dokumentieren.
mehrzeiliger String: Eine in dreifachen Anführungszeichen eingeschlossene Zeichenkette, die sich über mehr als eine Zeile eines Programms erstrecken kann.
Vorbedingung: Eine Bedingung, die vom Aufrufer erfüllt werden muss, bevor eine Funktion gestartet wird.
Nachbedingung: Eine Bedingung, die von der Funktion erfüllt werden sollte, bevor sie endet.
Übungen
Für diese Übungen gibt es ein paar weitere Schildkrötenfunktionen, die du vielleicht verwenden möchtest:
penup
-
Hebe den imaginären Stift der Schildkröte an, damit sie keine Spuren hinterlässt, wenn sie sich bewegt.
pendown
Die folgende Funktion verwendet penup
und pendown
, um die Schildkröte zu bewegen, ohne eine Spur zu hinterlassen:
from
jupyturtle
import
penup
,
pendown
def
jump
(
length
):
"""Move forward length units without leaving a trail.
Postcondition: Leaves the pen down.
"""
penup
()
forward
(
length
)
pendown
()
Übung
Schreibe eine Funktion namens rhombus
, die einen Rhombus mit einer bestimmten Seitenlänge und einem bestimmten Innenwinkel zeichnet. Hier ist zum Beispiel ein Rhombus mit einer Seitenlänge von 50
und einem Innenwinkel von 60
Grad:
Übung
Schreibe nun eine allgemeinere Funktion namens parallelogram
, die ein Viereck mit parallelen Seiten zeichnet. Dann schreibe rectangle
und rhombus
um und verwende parallelogram
.
Übung
Schreibe einen entsprechend allgemeinen Satz von Funktionen, die solche Formen zeichnen können.
Tipp: Schreibe eine Funktion namens triangle
, die ein Dreieckssegment zeichnet, und dann eine Funktion namens draw_pie
, die triangle
verwendet.
Übung
Schreibe einen entsprechend allgemeinen Satz von Funktionen, die Blumen wie diese zeichnen können.
Tipp: Verwende arc
, um eine Funktion namens petal
zu schreiben, die ein Blütenblatt zeichnet.
Frag einen virtuellen Assistenten
Einige Module wie jupyturtle
in Python und das Modul, das wir in diesem Kapitel verwendet haben, wurden für dieses Buch angepasst. Wenn du also einen virtuellen Assistenten um Hilfe bittest, wird er nicht wissen, welches Modul er verwenden soll. Aber wenn du ihm ein paar Beispiele gibst, mit denen er arbeiten kann, wird er es wahrscheinlich herausfinden. Probiere zum Beispiel diese Eingabeaufforderung aus und prüfe, ob er eine Funktion schreiben kann, die eine Spirale zeichnet:
The following program uses a turtle graphics module to draw a circle: from jupyturtle import make_turtle, forward, left import math def polygon(n, length): angle = 360 / n for i in range(n): forward(length) left(angle) def circle(radius): circumference = 2 * math.pi * radius n = 30 length = circumference / n polygon(n, length) make_turtle(delay=0) circle(30) Write a function that draws a spiral.
Denke daran, dass das Ergebnis möglicherweise Funktionen verwendet, die wir noch nicht kennen, und dass es Fehler haben könnte. Kopiere den Code des virtuellen Assistenten und schau, ob du ihn zum Laufen bringst. Wenn du nicht das bekommen hast, was du wolltest, versuche, die Eingabeaufforderung zu ändern.
Get Think Python, 3. Auflage now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.