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 m⁠a⁠k⁠e⁠_​t⁠u⁠r⁠t⁠l⁠e 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:

  1. Beginne damit, ein kleines Programm ohne Funktionsdefinitionen zu schreiben.

  2. 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.

  3. Verallgemeinere die Funktion, indem du geeignete Parameter hinzufügst.

  4. Wiederhole die Schritte 1 bis 3, bis du eine Reihe von funktionierenden Funktionen hast.

  5. 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

Wie die Funktion tut, was sie tun soll

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

Leg den Stift wieder hin.

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 mit dem Namen rectangle, die ein Rechteck mit bestimmten Seitenlängen zeichnet. Hier ist zum Beispiel ein Rechteck, das 80 Einheiten breit und 40 Einheiten hoch ist:

Ü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.