{ "cells": [ { "cell_type": "markdown", "id": "1331faa1", "metadata": {}, "source": [ "# Kapitel 4: Funktionen und Schnittstellen\n", "[Chapter 4: Functions and Interfaces](https://colab.research.google.com/github/AllenDowney/ThinkPython/blob/v3/chapters/chap04.ipynb)\n", "\n", "Dieses Kapitel führt ein Modul namens `jupyturtle` ein, mit dem wir einfache Zeichnungen erstellen können, indem wir einer imaginären Schildkröte (Englisch: _turtle_) Anweisungen geben.\n", "Wir werden dieses Modul verwenden, um Funktionen zu schreiben, die Quadrate, Polygone (_Vielecke_) und Kreise zeichnen -- und um **Schnittstellenentwurf** (Englisch: _interface design_) zu demonstrieren, was eine Art ist, zusammenarbeitende Funktionen zu entwerfen.\n", "\n", "**Bevor Sie mit diesem Notebook starten, sollten Sie wiederholen, was Sie im letzten Notebook gelernt haben. Gehen Sie zurück und schauen Sie sich mindestens das Glossar an und wiederholen Sie die dort genannten Begriffe.**" ] }, { "cell_type": "markdown", "id": "793c0bd2-9068-48a1-886f-22983ef698a4", "metadata": {}, "source": [ "## Ihre Lernziele\n", "Sie können eine Übersicht der Inhalte dieses Notebooks einblenden mit *Strg + Shift + k*. \n", "\n", "Beschreiben Sie in 2-3 Stichpunkten kurz was Sie im Seminar heute lernen wollen. Klicken Sie dazu doppelt auf diesen Text und bearbeiten Sie dann den Text:\n", "\n", "- \n", "- \n", "- \n", "\n", "\n", "\n", "## Exkurs: Was mir an Python gefällt\n", "\n", "Man kann schnell und einfach ein Programm aufschreiben und testen. Man muss es weder kompilieren, noch viel \"unnötige\" Syntax kennen:" ] }, { "cell_type": "code", "execution_count": null, "id": "8a924dee-19a8-4041-9eed-e80b9de21904", "metadata": {}, "outputs": [], "source": [ "def factorial(n):\n", " if n < 2:\n", " return 1\n", " else:\n", " return n * factorial(n - 1)\n", "\n", "print(factorial(5))" ] }, { "cell_type": "markdown", "id": "7f862818-7ff3-48a5-ba1c-bef4cba625f4", "metadata": {}, "source": [ "## Herunterladen des unterstützenden Codes\n", "Die folgende Zelle lädt eine Datei herunter und führt einen Code aus, der speziell für dieses Notebook verwendet wird. Sie müssen diesen Code nicht verstehen, aber Sie sollten die Zelle vor allen weiteren Zellen in diesem Notebook ausführen." ] }, { "cell_type": "code", "execution_count": null, "id": "df64b7da", "metadata": { "tags": [ "remove-cell" ] }, "outputs": [], "source": [ "from os.path import basename, exists\n", "\n", "def download(url):\n", " filename = basename(url)\n", " if not exists(filename):\n", " from urllib.request import urlretrieve\n", "\n", " local, _ = urlretrieve(url, filename)\n", " print(\"Downloaded \" + str(local))\n", " return filename\n", "\n", "download('https://github.com/AllenDowney/ThinkPython/raw/v3/thinkpython.py');\n", "download('https://github.com/AllenDowney/ThinkPython/raw/v3/diagram.py');\n", "download('https://github.com/ramalho/jupyturtle/releases/download/2024-03/jupyturtle.py');\n", "\n", "import thinkpython\n", "\n", "%load_ext autoreload\n", "%autoreload 2" ] }, { "cell_type": "markdown", "id": "0b0efa00", "metadata": { "tags": [ "section_turtle_module" ] }, "source": [ "## Das jupyturtle-Modul\n", "\n", "Um das `jupyturtle`-Modul zu verwenden, können wir es folgendermaßen importieren:" ] }, { "cell_type": "code", "execution_count": null, "id": "8f5a8a45", "metadata": {}, "outputs": [], "source": [ "import jupyturtle" ] }, { "cell_type": "markdown", "id": "8c801121", "metadata": {}, "source": [ "Jetzt können wir die im Modul definierten Funktionen wie etwa `make_turtle` und `forward` nutzen:" ] }, { "cell_type": "code", "execution_count": null, "id": "b3f255cd", "metadata": {}, "outputs": [], "source": [ "jupyturtle.make_turtle()\n", "jupyturtle.forward(100)" ] }, { "cell_type": "markdown", "id": "77a61cbb", "metadata": {}, "source": [ "`make_turtle` erstellt ein **Canvas** (dt.: _Leinwand_), also einen Bereich auf dem Bildschirm, in den wir zeichnen können, und eine Schildkröte/Turtle, die durch einen runden Panzer und einen dreieckigen Kopf dargestellt wird.\n", "Der Kreis zeigt die Position der Schildkröte/Turtle an und das Dreieck gibt die Richtung an, in die sie schaut.\n", "\n", "`forward` bewegt die Schildkröte/Turtle eine vorgegebene Strecke in die Richtung, in die sie gerade zeigt und hinterlässt dabei ein Stück einer Linie auf ihrer Bewegungsbahn.\n", "Die Distanz der Strecke wird in willkürlichen Einheiten angegeben -- die tatsächliche Größe hängt vom Bildschirm Ihres Computers ab.\n", "\n", "Wir werden im `jupyturtle`-Modul definierte Funktionen oft verwenden, daher wäre es praktisch, nicht jedes Mal den Namen des Moduls schreiben zu müssen.\n", "Das ist möglich, wenn wir das Modul auf diese Weise importieren:" ] }, { "cell_type": "code", "execution_count": null, "id": "234fde81", "metadata": {}, "outputs": [], "source": [ "from jupyturtle import make_turtle, forward" ] }, { "cell_type": "markdown", "id": "c1322d31", "metadata": {}, "source": [ "Diese Version der import-Anweisung importiert `make_turtle` und `forward` aus dem `jupyturtle`-Modul, sodass wir diese folgendermaßen aufrufen können:" ] }, { "cell_type": "code", "execution_count": null, "id": "1e768880", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "forward(100)" ] }, { "cell_type": "markdown", "id": "bd319754", "metadata": {}, "source": [ "`jupyturtle` stellt zwei weitere Funktionen namens `left` und `right` zur Verfügung, die wir verwenden werden. Wir importieren sie so:" ] }, { "cell_type": "code", "execution_count": null, "id": "6d874b03", "metadata": {}, "outputs": [], "source": [ "from jupyturtle import left, right" ] }, { "cell_type": "markdown", "id": "0da2a311", "metadata": {}, "source": [ "`left` bringt die Schildkröte/Turtle dazu, sich nach links zu drehen. Es nimmt ein Argument auf, das ist in diesem Fall der Winkel (Englisch: _angle_) der Drehung in Grad.\n", "So können wir zum Beispiel eine 90-Grad-Drehung nach links ausführen:" ] }, { "cell_type": "code", "execution_count": null, "id": "1bb57a0c", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "forward(50)\n", "left(90)\n", "forward(50)" ] }, { "cell_type": "markdown", "id": "cea2940f", "metadata": {}, "source": [ "Dieses Programm bewegt die Schildkröte/Turtle erst nach Osten und dann nach Norden und hinterlässt auf dem Weg zwei Linienstücke.\n", "Bevor Sie das Notebook weiterbearbeiten, versuchen Sie, ob Sie das vorherige Programm so anpassen können, dass das Ergebnis ein Quadrat ist.\n", "\n", "\n", "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/jasoneppink/4964471335\" title=\"Spoiler Alert\"><img src=\"https://farm5.staticflickr.com/4110/4964471335_1f86a923f3_n.jpg\" width=\"320\" height=\"213\" alt=\"Spoiler Alert\"></a><script async src=\"//embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>\n", "\n", "(Quelle: Jason Eppink, Flickr)\n" ] }, { "cell_type": "markdown", "id": "e20ea96c", "metadata": {}, "source": [ "## Ein Quadrat erstellen\n", "\n", "Hier ist ein Weg, um ein Quadrat zu erstellen:" ] }, { "cell_type": "code", "execution_count": null, "id": "9a9e455f", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "\n", "forward(50)\n", "left(90)\n", "\n", "forward(50)\n", "left(90)\n", "\n", "forward(50)\n", "left(90)\n", "\n", "forward(50)\n", "left(90)" ] }, { "cell_type": "markdown", "id": "a7500957", "metadata": {}, "source": [ "Da dieses Programm die gleichen zwei Zeilen viermal wiederholt, können wir das gleiche Ergebnis auch kurz und bündig durch eine `for`-Schleife erreichen." ] }, { "cell_type": "code", "execution_count": null, "id": "cc27ad66", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "for i in range(4):\n", " forward(50)\n", " left(90)" ] }, { "cell_type": "markdown", "id": "c072ea41", "metadata": { "tags": [ "section_encapsulation" ] }, "source": [ "## Verkapselung und Verallgemeinerung\n", "\n", "Lasst uns nun den Code zum Zeichnen von Quadraten aus dem vorherigen Abschnitt nehmen und ihn in eine Funktion namens `square` einfügen:" ] }, { "cell_type": "code", "execution_count": null, "id": "ad5f1128", "metadata": {}, "outputs": [], "source": [ "def square():\n", " for i in range(4):\n", " forward(50)\n", " left(90)" ] }, { "cell_type": "markdown", "id": "0789b5d9", "metadata": {}, "source": [ "Jetzt können wir die Funktion so aufrufen:" ] }, { "cell_type": "code", "execution_count": null, "id": "193bbe5e", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "square()" ] }, { "cell_type": "markdown", "id": "da905fc6", "metadata": {}, "source": [ "Ein Codestück in eine Funktion einzupacken nennt man **Verkapselung** (Englisch: _encapsulation_).\n", "Ein Vorteil der Verkapselung ist, dass sie dem Code einen Namen gibt, 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 Rumpf zu kopieren und wieder einzufügen.\n", "\n", "\n", "\n", "\n", "([Code Quality](https://xkcd.com/1513/), Randall Munroe) [Erklärung des Comics](https://www.explainxkcd.com/wiki/index.php/1513:_Code_Quality) falls Sie mehr lesen wollen.\n", "\n", "In der aktuellen Version ist die Größe des Quadrates immer `50`.\n", "Wenn wir Quadrate in verschiedenen Größen zeichnen wollen, können wir die Seitenlänge als Parameter verwenden:" ] }, { "cell_type": "code", "execution_count": null, "id": "def8a5f1", "metadata": {}, "outputs": [], "source": [ "def square(length):\n", " for i in range(4):\n", " forward(length)\n", " left(90)" ] }, { "cell_type": "markdown", "id": "397fda4b", "metadata": {}, "source": [ "Jetzt können wir Quadrate in verschiedenen Größen zeichnen:" ] }, { "cell_type": "code", "execution_count": null, "id": "b283e795", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "square(30)\n", "square(60)" ] }, { "cell_type": "markdown", "id": "5a46bf64", "metadata": {}, "source": [ "Zu einer Funktion einen Parameter hinzuzufügen wird **Verallgemeinerung** (Englisch: _generalization_) genannt, da dabei die Funktion allgemeiner gemacht wird: in der vorherigen Version war das Quadrat immer gleich groß; in dieser Version kann es jede beliebige Größe haben.\n", "\n", "Wenn wir einen weiteren Parameter hinzufügen, können wir die Funktion sogar noch allgemeiner machen.\n", "Die folgende Funktion zeichnet regelmäßige Polygone (Vielecke mit gleichen Seitenlängen und Innenwinkeln) mit einer vorgegebenen Anzahl an Seiten:" ] }, { "cell_type": "code", "execution_count": null, "id": "171974ed", "metadata": {}, "outputs": [], "source": [ "def polygon(n, length):\n", " angle = 360 / n\n", " for i in range(n):\n", " forward(length)\n", " left(angle)" ] }, { "cell_type": "markdown", "id": "286d3c77", "metadata": {}, "source": [ "In einem regelmäßigen Polygon mit `n` Seiten beträgt der Winkel zwischen zwei anliegenden Seiten `360 / n` Grad. \n", "\n", "Das folgende Beispiel zeichnet ein `7`-seitiges Polygon mit einer Seitenlänge von `30`:" ] }, { "cell_type": "code", "execution_count": null, "id": "71f7d9d2", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "polygon(7, 30)" ] }, { "cell_type": "markdown", "id": "dc0226db", "metadata": {}, "source": [ "Wenn eine Funktion mehr als ein paar numerische Argumente hat, vergisst man leicht, welche diese sind oder in welche Reihenfolge sie gehören.\n", "Es kann daher eine gute Idee sein, die Namen der Parameter in die Liste mit Argumenten einzubauen:" ] }, { "cell_type": "code", "execution_count": null, "id": "8ff2a5f4", "metadata": { "tags": [ "remove-output" ] }, "outputs": [], "source": [ "make_turtle()\n", "polygon(n=7, length=30)" ] }, { "cell_type": "markdown", "id": "6aa28eba", "metadata": {}, "source": [ "Diese werden manchmal als **\"benannte Argumente\"** bezeichnet, da sie die Namen der Parameter enthalten.\n", "In Python nennt man sie aber auch öfter **Schlüsselwort-Argumente** (nicht zu verwechseln mit Python-Schlüsselwörtern wie `for`und `def`).\n", "\n", "Diese Verwendung des Zuweisungsoperators, `=`, ist eine Erinnerung daran, wie Argumente und Parameter funktionieren -- wenn Sie eine Funktion aufrufen, werden die Argumente den Parametern zugewiesen." ] }, { "cell_type": "markdown", "id": "b10184b4", "metadata": {}, "source": [ "## Annäherung an einen Kreis\n", "\n", "Nehmen wir einmal an, wir wollen einen Kreis zeichnen.\n", "Wir können das ungefähr erreichen, indem wir ein Polygon mit so vielen Seiten zeichnen, dass die einzelnen Seiten schwer zu erkennen sind.\n", "Hier ist eine Funktion, die `polygon` verwendet, um ein `30`-seitiges Polygon zu zeichnen, das sich an einen Kreis annähert:" ] }, { "cell_type": "code", "execution_count": null, "id": "7f2a5f28", "metadata": {}, "outputs": [], "source": [ "import math\n", "\n", "def circle(radius):\n", " circumference = 2 * math.pi * radius\n", " n = 30\n", " length = circumference / n\n", " polygon(n, length)" ] }, { "cell_type": "markdown", "id": "39023314", "metadata": {}, "source": [ "`circle` nimmt den Radius des Kreises als Parameter auf.\n", "Die Funktion berechnet `circumference`, also den Umfang eines Kreises mit dem angegebenen Radius.\n", "`n` ist die Anzahl an Seiten, also ist `circumference / n` die Länge jeder einzelnen Seite.\n", "\n", "Diese Funktion braucht möglicherweise relativ viel Zeit um vollständig abzulaufen.\n", "Wir können den Vorgang beschleunigen, indem wir `make_turtle` mit einem Schlüsselwort-Argument namens `delay` aufrufen, das die Zeit in Sekunden festlegt, die die Schildkröte/Turtle nach jedem Schritt wartet.\n", "Der Standardwert sind `0.2` Sekunden -- wenn wir ihn auf `0.02` setzen, läuft die Funktion etwa 10 mal schneller:" ] }, { "cell_type": "code", "execution_count": null, "id": "75258056", "metadata": {}, "outputs": [], "source": [ "make_turtle(delay=0.02)\n", "circle(30)" ] }, { "cell_type": "markdown", "id": "701f9cf8", "metadata": {}, "source": [ "Eine Einschränkung dieser Lösung ist, dass `n` eine Konstante ist, das bedeutet, dass für sehr große Kreise die Seiten zu lang sind und wir bei kleinen Kreisen Zeit damit verschwenden, sehr kurze Seiten zu zeichnen.\n", "Eine Option wäre es, die Funktion durch die Verwendung von `n` als Parameter zu verallgemeinern.\n", "Aber im Moment halten wir es lieber noch einfach." ] }, { "cell_type": "markdown", "id": "c48f262c", "metadata": {}, "source": [ "## Refactoring\n", "\n", "Lasst uns nun eine allgemeinere Version von `circle` namens `arc` schreiben, die einen zweiten Parameter, `angle`, aufnimmt und einen Kreisbogen zeichnet, der den gegebenen Winkel umspannt.\n", "Wenn `angle` beispielsweise `360` Grad ist, wird ein vollständiger Kreis gezeichnet. Wenn `angle` `180` Grad ist, wird ein Halbkreis gezeichnet.\n", "\n", "Um `circle` zu schreiben, konnten wir `polygon` wiederverwenden, da ein vielseitiges Polygon eine gute Annäherung an einen Kreis ist.\n", "Aber wir können `polygon` nicht verwenden, um `arc` zu schreiben.\n", "\n", "Stattdessen erstellen wir eine allgemeinere Version von `polygon` namens `polyline`:" ] }, { "cell_type": "code", "execution_count": null, "id": "381edd23", "metadata": {}, "outputs": [], "source": [ "def polyline(n, length, angle):\n", " for i in range(n):\n", " forward(length)\n", " left(angle)" ] }, { "cell_type": "markdown", "id": "c2b2503e", "metadata": {}, "source": [ "`polyline` nimmt als Parameter die Anzahl der zu zeichnenden Linienstücke, `n`, die Länge dieser Segmente, `length`, und den Winkel zwischen ihnen, `angle`, auf.\n", "\n", "Nun können wir `polygon` so umschreiben, dass `polyline` darin verwendet wird:" ] }, { "cell_type": "code", "execution_count": null, "id": "2f4eecc0", "metadata": {}, "outputs": [], "source": [ "def polygon(n, length):\n", " angle = 360.0 / n\n", " polyline(n, length, angle)" ] }, { "cell_type": "markdown", "id": "2714a59e", "metadata": {}, "source": [ "Und wir können `polyline` verwenden, um `arc` zu schreiben:" ] }, { "cell_type": "code", "execution_count": null, "id": "539466f6", "metadata": {}, "outputs": [], "source": [ "def arc(radius, angle):\n", " arc_length = 2 * math.pi * radius * angle / 360\n", " n = 30\n", " length = arc_length / n\n", " step_angle = angle / n\n", " polyline(n, length, step_angle)" ] }, { "cell_type": "markdown", "id": "3c18773c", "metadata": {}, "source": [ "`arc` ist `circle` ähnlich, mit dem Unterschied, dass es `arc_length` berechnet, was ein Anteil des Kreisumfangs ist.\n", "\n", "Schließlich können wir `circle` jetzt so umschreiben, das `arc` darin verwendet wird:" ] }, { "cell_type": "code", "execution_count": null, "id": "8e09f456", "metadata": {}, "outputs": [], "source": [ "def circle(radius):\n", " arc(radius, 360)" ] }, { "cell_type": "markdown", "id": "313a357c", "metadata": {}, "source": [ "Um zu überprüfen, dass die Funktionen so funktionieren wie erwartet, verwenden wir sie, um so etwas wie eine Schnecke zu zeichnen.\n", "Mit `delay=0` läuft die Schildkröte/Turtle so schnell wie möglich:" ] }, { "cell_type": "code", "execution_count": null, "id": "80d6eadd", "metadata": {}, "outputs": [], "source": [ "make_turtle(delay=0)\n", "polygon(n=20, length=9)\n", "arc(radius=70, angle=70)\n", "circle(radius=10)" ] }, { "cell_type": "markdown", "id": "a34da3d8", "metadata": {}, "source": [ "In diesem Beispiel haben wir mit funktionierendem Code begonnen und diesen mit verschiedenen Funktionen umstrukturiert. Solche Veränderungen, die den Code verbessern, ohne sein Verhalten zu ändern, nennt man **Refactoring** (dt. etwa: _Umstrukturierung_).\n", "\n", "Wenn wir vorausgeplant hätten, hätten wir vielleicht `polyline`zuerst geschrieben und so das Refactoring vermieden, aber oft weiß man zu Beginn eines Projekts noch nicht genug, um alle Funktionen zu entwerfen.\n", "Sobald man mit dem Programmieren beginnt, verstehen man das Problem besser.\n", "Manchmal ist Refactoring ein Zeichen dafür, dass man etwas dazugelernt hat." ] }, { "cell_type": "markdown", "id": "d18c9d16", "metadata": {}, "source": [ "## Stapel-Diagramm\n", "\n", "Wenn wir `circle` aufrufen, ruft dieses `arc`, welches wiederum `polyline`aufruft.\n", "Wir können ein Stapel-Diagramm verwenden, um diese Abfolge von Funktionsaufrufen und deren einzelne Parameter zu zeigen:" ] }, { "cell_type": "code", "execution_count": null, "id": "1571ee71", "metadata": { "tags": [ "remove-cell" ] }, "outputs": [], "source": [ "from diagram import make_binding, make_frame, Frame, Stack\n", "\n", "frame1 = make_frame(dict(radius=30), name='circle', loc='left')\n", "\n", "frame2 = make_frame(dict(radius=30, angle=360), name='arc', loc='left', dx=1.1)\n", "\n", "frame3 = make_frame(dict(n=60, length=3.04, angle=5.8), \n", " name='polyline', loc='left', dx=1.1, offsetx=-0.27)\n", "\n", "stack = Stack([frame1, frame2, frame3], dy=-0.4)" ] }, { "cell_type": "code", "execution_count": null, "id": "f4e37360", "metadata": { "tags": [ "remove-input" ] }, "outputs": [], "source": [ "from diagram import diagram, adjust\n", "\n", "width, height, x, y = [3.58, 1.31, 0.98, 1.06]\n", "ax = diagram(width, height)\n", "bbox = stack.draw(ax, x, y)\n", "#adjust(x, y, bbox)" ] }, { "cell_type": "markdown", "id": "3160bba1", "metadata": {}, "source": [ "Hier ist zu beachten, dass der Wert von `angle` in `polyline` ein anderer ist als der Wert von `angle` in `arc`.\n", "Parameter sind lokal, das bedeutet wir können den gleichen Parameternamen in verschiedenen Funktionen verwenden; in jeder Funktion handelt es sich dann um eine andere Variable, die sich auf einen anderen Wert beziehen kann." ] }, { "cell_type": "markdown", "id": "d6ab58ec-6e77-45d3-8096-5f9d4317cb4c", "metadata": {}, "source": [ "## Ein Entwicklungsplan\n", "\n", "Ein **Entwicklungsplan** ist ein Prozess zum Schreiben von Programmen. Der Prozess, den wir in dieser Fallstudie verwendet haben, heißt \"Verkapselung und Verallgemeinerung\". Die Schritte dieses Prozesses sind:\n", "\n", "1. Wir schreiben ein kleines Programm ohne Funktionsdefinitionen.\n", "2. Sobald das Programm funktioniert, identifizieren wir ein schlüssiges und zusammengehöriges Stück Code, verkapseln es in einer Funktion und geben ihm einen Namen.\n", "3. Wir verallgemeinern die Funktion durch Hinzufügen geeigneter Parameter.\n", "4. Wir wiederholen die Schritte 1 bis 3 bis wir eine Menge funktionierender Funktionen haben. Dabei kopieren wir Code und fügen ihn an der richtigen Stelle ein, um nochmaliges Eintippen zu vermeiden (und das damit ggf. verbundene Debugging).\n", "5. Wir suchen nach Möglichkeiten, das Programm durch Refactoring zu verbessern. Wenn wir beispielsweise ähnlichen Code an verschiedenen Stellen haben, können wir überlegen, ob wir ihn nicht besser in eine geeignete allgemeine Funktion ausklammern. \n", "\n", "\n", "Dieser Prozess hat einige Nachteile (wir schauen uns Alternativen später an), aber er ist praktisch, wenn wir vorab nicht wissen, wie wir das Programm in Funktionen aufteilen könnten. Dieser Ansatz ermöglicht uns, das Programm zu entwerfen, während wir es schreiben.\n" ] }, { "cell_type": "markdown", "id": "a3b6b83d", "metadata": {}, "source": [ "Der Entwurf einer Funktion hat zwei Teile:\n", "\n", "* Die **Schnittstelle** ist die Art, wie eine Funktion verwendet wird und umfasst ihren Namen, die Parameter, die sie aufnimmt und die Aufgaben, die die Funktion erfüllen soll.\n", "\n", "* Die **Implementierung** ist, auf welche Weise die Funktion das tut, was sie tun soll.\n", "\n", "Hier ist beispielsweise die erste Version von `circle`, die wir geschrieben haben, in der `polygon` verwendet wird:" ] }, { "cell_type": "code", "execution_count": null, "id": "baf964ba", "metadata": {}, "outputs": [], "source": [ "def circle(radius):\n", " circumference = 2 * math.pi * radius\n", " n = 30\n", " length = circumference / n\n", " polygon(n, length)" ] }, { "cell_type": "markdown", "id": "5d3d2e79", "metadata": {}, "source": [ "Und hier ist die Version nach dem Refactoring, die `arc`verwendet:" ] }, { "cell_type": "code", "execution_count": null, "id": "e2e006d5", "metadata": {}, "outputs": [], "source": [ "def circle(radius):\n", " arc(radius, 360)" ] }, { "cell_type": "markdown", "id": "b726f72c", "metadata": {}, "source": [ "Diese zwei Funktionen haben die gleiche Schnittstelle -- sie nehmen die gleichen Parameter auf und erfüllen die gleiche Aufgabe -- aber sie haben unterschiedliche Implementierungen." ] }, { "cell_type": "markdown", "id": "3e3bae20", "metadata": { "tags": [ "section_docstring" ] }, "source": [ "## Docstrings\n", "\n", "Ein **Docstring** ist eine Zeichenkette am Anfang einer Funktion, die die Schnittstelle der Funktion erklärt (\"doc\" ist kurz für \"documentation\", also Dokumentation). Hier ist ein Beispiel:" ] }, { "cell_type": "code", "execution_count": null, "id": "b68f3682", "metadata": {}, "outputs": [], "source": [ "def polyline(n, length, angle):\n", " \"\"\"Zeichnet Linienstücke mit vorgegebenen Längen und Winkeln zwischen ihnen.\n", " \n", " n: ganzzahlige Menge von Linienstücken\n", " length: Länge der Linienstücke\n", " angle: Winkel zwischen den Linienstücken (in Grad)\n", " \"\"\" \n", " for i in range(n):\n", " forward(length)\n", " left(angle)" ] }, { "cell_type": "markdown", "id": "55b60cbc", "metadata": {}, "source": [ "Es gilt die Konvention, dass Docstrings in dreifachen Anführungszeichen gesetzt werden. Mit Hilfe der dreifachen Anführungszeichen kann der Text über mehr als eine Zeile hinwegreichen, dies ist also eine **mehrzeilige Zeichenkette**.\n", "\n", "Ein Docstring sollte:\n", "\n", "* kurz und bündig erklären, was die Funktion tut, ohne zu sehr auf die Details der Funktionsweise einzugehen,\n", "\n", "* erklären, welchen Einfluss die verschiedenen Parameter auf das Verhalten der Funktion haben und\n", "\n", "* angeben, was der Datentyp jedes Parameters sein soll, falls es nicht offensichtlich ist.\n", "\n", "Das Schreiben dieser Art von Dokumentation ist ein wichtiger Teil des Schnittstellenentwurfs. Eine gut konzipierte Schnittstelle sollte einfach zu erklären sein. Falls es Ihnen schwerfällt, Ihre Funktionen zu erklären, dann könnte es vielleicht helfen, die Schnittstelle zu verbessern. " ] }, { "cell_type": "markdown", "id": "f1115940", "metadata": { "jp-MarkdownHeadingCollapsed": true }, "source": [ "## Debugging\n", "\n", "Eine Schnittstelle ist wie ein Vertrag zwischen der Funktion und den Aufrufenden. Die Aufrufenden stimmen zu, bestimmte Parameter bereitzustellen und die Funktion stimmt zu, eine bestimmte Aufgabe zu erledigen.\n", "\n", "Beispielsweise benötigt die Funktion `polyline` drei Parameter:\n", "- `n` muss eine ganze Zahl sein,\n", "- `length` muss eine positive Zahl sein,\n", "- `angle` muss eine Zahl sein, die einen Winkel in Grad darstellt.\n", "\n", "Diese Anforderungen nennen wir auch **Vorbedingungen** (Englisch: _preconditions_), denn es wird vorausgesetzt, dass sie erfüllt sind, bevor die Funktion ausgeführt wird. Umgekehrt heißen Bedingungen am Ende einer Funktion **Nachbedingungen** (Englisch: _postconditions_). Nachbedingungen schließen die beabsichtigte Wirkung der Funktion (wie z.B. ein Linienstück zeichnen) und etwaige Nebeneffekte (wie z.B. die Schildkröte/Turtle zu bewegen oder andere Änderungen vorzunehmen) ein. \n", "\n", "Die Erfüllung der Vorbedingungen ist die Aufgabe der Aufrufenden. Wenn die Aufrufenden eine (ordentlich dokumentierte!) Vorbedingung verletzen und die Funktion nicht richtig arbeitet, dann liegt der Fehler bei den Aufrufenden, nicht bei der Funktion.\n", "\n", "Wenn die Vorbedingungen erfüllt sind, die Nachbedingungen aber nicht, dann liegt der Fehler bei der Funktion. Wenn ihre Vor- und Nachbedingungen klar und deutlich sind, dann kann das sehr beim Debugging helfen." ] }, { "cell_type": "markdown", "id": "a4d33a70", "metadata": {}, "source": [ "## Glossar\n", "- Schnittstellenentwurf:\n", "- Canvas:\n", "- Verkapselung:\n", "- Verallgemeinerung:\n", "- Schlüsselwort-Argument:\n", "- Refactoring:\n", "- Entwicklungsplan:\n", "- Docstring:\n", "- mehrzeilige Zeichenkette:\n", "- Vorbedingung:\n", "- Nachbedingung:" ] }, { "cell_type": "markdown", "id": "0bfe2e19", "metadata": {}, "source": [ "## Übung" ] }, { "cell_type": "code", "execution_count": null, "id": "9f94061e", "metadata": { "tags": [ "remove-print" ] }, "outputs": [], "source": [ "# Diese Zelle weist Jupyter an, detallierte Debugging-Informationen auszugeben, wenn ein Laufzeitfehler\n", "# passiert. Lassen Sie sie daher laufen, bevor Sie beginnen an den Aufgaben zu arbeiten.\n", "\n", "%xmode Verbose" ] }, { "cell_type": "markdown", "id": "50ed5c38", "metadata": {}, "source": [ "Für die Übungen weiter unten gibt es noch einige weitere Schildkröten/Turtle-Funktionen, die Sie vielleicht benutzen möchten.\n", "\n", "* `penup` hebt den imaginären Stift der Schildkröte/Turtle an, sodass bei der weiteren Fortbewegung keine Spur mehr hinterlassen wird.\n", "* `pendown` setzt den Stift wieder ab.\n", "\n", "Die folgende Funktion nutzt `penup` und `pendown`, um die Schildkröte/Turtle zu bewegen, ohne dass diese eine Spur hinterlässt:" ] }, { "cell_type": "code", "execution_count": null, "id": "6f9a0106", "metadata": {}, "outputs": [], "source": [ "from jupyturtle import penup, pendown\n", "\n", "def jump(length):\n", " \"\"\"Längeneinheiten vorwärts bewegen, ohne eine Spur zu hinterlassen.\n", " \n", " Nachbedingung: Endet mit abgesetztem Stift.\n", " \"\"\"\n", " penup()\n", " forward(length)\n", " pendown()" ] }, { "cell_type": "markdown", "id": "92e4568e-86dc-42a7-80a6-2c9f66804da9", "metadata": {}, "source": [ "Außerdem können Sie bei Initialisierung einer Schildkröte ihre Geschwindigkeit mit dem `delay` bestimmen. Bspw. so: `jupyturtle.Turtle(delay=0.01)`. Das ist hilfreich bei Zeichnungen, die sehr viele Schritte benötigen. Nehmen Sie einen Wert zwischen 0.01 und 0.05." ] }, { "cell_type": "markdown", "id": "c78c1e17", "metadata": {}, "source": [ "### Aufgabe 1\n", "\n", "Schreiben Sie eine Funktion namens `rectangle`, die ein Rechteck mit vorgegebenen Seitenlängen zeichnet.\n", "Hier wird beispielsweise ein Rechteck erstellt, das `80` Einheiten breit und `40` Einheiten hoch ist:" ] }, { "cell_type": "code", "execution_count": null, "id": "c54ba660", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "4b05078c", "metadata": { "tags": [ "remove-cell" ] }, "source": [ "Sie können den folgenden Code verwenden, um Ihre Funktion zu testen:" ] }, { "cell_type": "code", "execution_count": null, "id": "1311ee08", "metadata": { "tags": [ "remove-input" ] }, "outputs": [], "source": [ "make_turtle()\n", "rectangle(80, 40)" ] }, { "cell_type": "markdown", "id": "8b8faaf6", "metadata": {}, "source": [ "### Aufgabe 2\n", "\n", "Schreiben Sie eine Funktion namens `rhombus`, die eine Raute mit vorgegebener Seitenlänge und Innenwinkel zeichnet.\n", "Hier wird beispielsweise eine Raute mit einer Seitenlänge von `50` Einheiten und einem Innnenwinkel von `60` Grad erstellt:" ] }, { "cell_type": "code", "execution_count": null, "id": "3db6f106", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "7917956b", "metadata": { "tags": [ "remove-cell" ] }, "source": [ "Sie können den folgenden Code verwenden, um Ihre Funktion zu testen:" ] }, { "cell_type": "code", "execution_count": null, "id": "1d845de9", "metadata": { "tags": [ "remove-input" ] }, "outputs": [], "source": [ "make_turtle()\n", "rhombus(50, 60)" ] }, { "cell_type": "markdown", "id": "a9175a90", "metadata": {}, "source": [ "### Aufgabe 3\n", "\n", "Schreiben Sie nun eine allgemeinere Funktion namens `parallelogram`, die ein Viereck mit parallelen Seiten zeichnet. Schreiben Sie dann `rectangle`und `rhombus` so um, dass diese `parallelogram`verwenden." ] }, { "cell_type": "code", "execution_count": null, "id": "895005cb", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "7e7d34b0", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "481396f9", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "c03bd4a2", "metadata": { "tags": [ "remove-cell" ] }, "source": [ "Sie können den folgenden Code verwenden, um Ihre Funktion zu testen:" ] }, { "cell_type": "code", "execution_count": null, "id": "c8dfebc9", "metadata": { "tags": [ "remove-input" ] }, "outputs": [], "source": [ "make_turtle(width=400)\n", "jump(-120)\n", "\n", "rectangle(80, 40)\n", "jump(100)\n", "rhombus(50, 60)\n", "jump(80)\n", "parallelogram(80, 50, 60)" ] }, { "cell_type": "markdown", "id": "d6608c25-680e-48a5-aaa3-4ee7ac4fa317", "metadata": {}, "source": [ "### Aufgabe 4\n", "\n", "Zeichnen Sie eine rechteckige Spirale (siehe Abbildung, die gestrichelten Linien dürfen gerne durchgezogen sein). \n", "\n", "a) Die Spirale hat eine fest angegebene Größe.\n", "\n", "b) Die Spirale kann durch einen Parameter in Ihrer Funktion variabel gestaltet werden.\n", "\n", " _________________\n", " | ____________\n", " | | ________ |\n", " | | | ____ | |\n", " | | | | _ | | |\n", " | | | |__| | | |\n", " | | |______| | |\n", " | |__________| |\n", " |______________|\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">1. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Wenn Sie sich die Spirale anschauen, sehen Sie, dass Sie einem Quadrat sehr ähnlich sieht. Was ist der Unterschied?\n", "\n", " </div> \n", "</details>\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">2. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Der Unterschied, ist, dass die Seiten nach jeder Ecke länger werden. Wie kann man das abbilden?\n", "\n", " </div> \n", "</details>\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">3. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Nutzen Sie einen Parameter, z.B. `length`, der innerhalb der Schleife erhöht wird, damit die turtle in jedem Schleifendurchgang ein Stück länger geradeaus läuft. \n", " \n", " </div> \n", "</details>\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">4. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Die Größe der Spirale lässt sich durch die Anzahl an Schleifendurchläufen angeben.\n", " \n", " </div> \n", "</details>\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "id": "5bfec923-f746-4d61-ac16-99bf0913124f", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "636a5c5b-42fb-4086-aa22-5c114769748c", "metadata": {}, "source": [ "\n", "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/jasoneppink/4964471335\" title=\"Spoiler Alert\"><img src=\"https://farm5.staticflickr.com/4110/4964471335_1f86a923f3_n.jpg\" width=\"320\" height=\"213\" alt=\"Spoiler Alert\"></a><script async src=\"//embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>\n", "\n", "(Quelle: Jason Eppink, Flickr)\n", "\n", "*(Hier ist eine mögliche [Lösung](https://github.com/AllenDowney/ThinkPython2/blob/master/code/pie.py))*" ] }, { "cell_type": "code", "execution_count": null, "id": "e877707b-3c1c-4788-8d71-fa30f55b5924", "metadata": {}, "outputs": [], "source": [ "import math\n", "import jupyturtle\n", "\n", "eve = jupyturtle.Turtle(delay=0.01)\n", "\n", "def rect_spiral(t, length, size):\n", " while length < size:\n", " t.forward(length)\n", " t.right(90)\n", " length = length + 2\n", "\n", "rect_spiral(eve, 1, 100)\n" ] }, { "cell_type": "markdown", "id": "feae252c", "metadata": {}, "source": [ "### Aufgabe 5\n", "\n", "Schreiben Sie ein ausreichend allgemeines Set an Funktionen, die solche Formen zeichnen können.\n", "\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">1. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Wir zeichnen die einzelnen \"Kuchenstücke\" bevor wir sie zu dem \"Kuchen\" zusammensetzen.\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">2. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Erzeugen Sie zunächst den Kopf der Funktion für ein einzelnes Kuchenstück. Überlegen Sie anschließend, wie Sie das Dreieck zeichnen könnten. Welche Informationen müssen dem Kopf der Funktion übergeben werden?\n", " \n", " </div> \n", "</details>\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">3. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Die Funktion für das Kuchenstück muss die Schildkröte, die Seitenlänge (die der Radius der Form \"Kuchen ist, daher nennen wir sie \"r\") und den Winkel übergeben bekommen, in dem das Kuchenstück gezeichnet werden soll. \n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">4. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Da wir die beiden Seitenlängen vorgeben, müssen wir die obere Länge in Abhängigkeit von den Seitenlängen (dem Radius der Form Kuchen und dem Winkel berechnen. Wir verwenden das sogenannte Bogenmaß. Die vollständige dafür Formel ist $2*r*sin(angle*\\pi/180)$ \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">5. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Um Herauszufinden, wie weit sich die Schildkröte drehen muss, ist es am Einfachsten verschiedene Drehwinkel auszuprobieren, bis es funktioniert. Verwenden Sie 90 Winkel als Basis und addieren oder subtrahieren Sie den Winkel aus dem Funktionskopf\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">6. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Damit die Schildkröte den gesamten Kuchen zeichnen kann muss Sie immer in derselben Position starten und enden. Dafür muss Sie sich zu Beginn im angegebenen Winkel drehen und am Ende soweit, dass sie wieder die Ausgangsposition annimmt. Um die Position wieder anzunehmen muss sich die Schildkröte um weniger als 180 Grad drehen. Der genaue Winkel kann mit Hilfe des Winkel-Parameters berechnet werden. \n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">7. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Um die Kuchen-Funktion zu schreiben können Sie die Blume-Funktion kopieren und anpassen. Es müssen einige Veränderungen vorgenommen werden. Überlegen Sie welche Änderungen das sind. Um einen Eindruck zu bekommen, können Sie Kuchen aufrufen und sehen welche Fehler auftreten.\n", "\n", " </div> \n", "</details>\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">8. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Damit der Kuchen ohne Lücken gezeichnet werden kann muss der Winkel berechnet werden und nicht der Funktion übergeben werden. Im Funktionskopf muss also nur der Radius der Figur, also die Seitenlänge der einzelnen Stücke, die Schildkröte und die Anzahl der Stücke übergeben werden. Der Winkel wird dann in Abhängigkeit von der Anzahl der Stücke berechnet.\n", "\n", " \n", " </div> \n", "</details>\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">9. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Der Winkel zwischen den Stücken ist, da wir eine volle Drehung vornehmen wollen 360 geteilt durch die Anzahl der Stücke. In der Schleife rufen wir Stück auf, dort müssen wir den Winkel anpassen, bevor wir die entsprechende Drehung vornehmen. Führen Sie die Funktion aus und versuchen Sie herauszufinden, wie Sie den Winkel in der Funktion für das Stück anpassen müssen.\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">10. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Der Winkel für Stück ist der halbe Winkel zwischen Stücken. Wir nehmen also die Winkelvariable/2 als zu übergebenden Wert.\n", "\n", " </div> \n", "</details>" ] }, { "cell_type": "code", "execution_count": null, "id": "22d0144b-4080-46f0-8895-b28685b2f27a", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "a6915bd7-a042-4156-8c34-048b52c84bad", "metadata": {}, "source": [ "\n", "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/jasoneppink/4964471335\" title=\"Spoiler Alert\"><img src=\"https://farm5.staticflickr.com/4110/4964471335_1f86a923f3_n.jpg\" width=\"320\" height=\"213\" alt=\"Spoiler Alert\"></a><script async src=\"//embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>\n", "\n", "(Quelle: Jason Eppink, Flickr)\n", "\n", "*(Hier ist eine mögliche [Lösung](https://github.com/AllenDowney/ThinkPython2/blob/master/code/pie.py))*" ] }, { "cell_type": "code", "execution_count": null, "id": "be1b7ed8", "metadata": {}, "outputs": [], "source": [ "import math\n", "import jupyturtle\n", "\n", "# Arbeiten Sie diese Lösung durch und kommentieren Sie die einzelnen Schritte\n", "\n", "def stück(t,r, angle):\n", " c = 2 * r * (math.sin(angle * math.pi / 180))\n", " t.right(angle)\n", " t.forward(r)\n", " t.left(90 + angle)\n", " t.forward(c)\n", " t.left(90 + angle)\n", " t.forward(r)\n", " t.left(180 - angle)\n", "\n", "def kuchen(t, n, r):\n", " angle = 360 / n\n", " for i in range (n):\n", " stück(t, r, angle / 2)\n", " t.left(angle)\n", " \n", "bob = jupyturtle.Turtle(delay=0.9) \n", "kuchen(bob, 15, 60)\n" ] }, { "cell_type": "markdown", "id": "7c665dd1", "metadata": {}, "source": [ "### Aufgabe 6\n", "\n", "\n", "\n", "Schreiben Sie eine möglichst allgemeine Menge an Funktionen zum Zeichnen von solchen Blumen.\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-info\">Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Die Lösung benötigt auch [polygon.py](http://thinkpython2.com/code/polygon.py) bzw. die Funktion `arc()` aus den obigen Übungen. \n", " \n", " </div> \n", "</details>\n", "\n", "Wie Sie inzwischen vielleicht erwarten folgen jetzt Hinweise dazu, wie Sie die Aufgabe angehen können. Versuchen Sie die Aufgabe in Partnerarbeit zu lösen und verwenden Sie so wenige Hinweise wie möglich, das ist die beste Übung: \n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">1. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Wenn Sie sich die Blume ansehen, können Sie feststellen, dass zwei Teile ineinander greifen um die Blume zu erzeugen. Welche Teile sind das?\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">2. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Die Blütenblätter werden einzeln hintereinander gezeichnet, konzentrieren Sie sich also zuerst darauf, ein einzelnes Blütenblatt zu zeichnen.\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">3. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Überlegen Sie wie der Kopf der Blütenblattfunktion aussehen muss und schreiben Sie diese. \n", " \n", " </div> \n", "</details>\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">4. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Sie müssen der Funktion eine Schildkröte übergeben, die das Blatt zeichnen soll. Die Funktion braucht auch den Radius des Kreisbogens, welcher die Länge des Blatts bestimmt und den Winkel in dem das Blatt gezeichnet werden soll und damit wie breit des Blütenblatt wird.\n", "\n", " \n", " </div> \n", "</details>\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">5. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Wie im vorherigen Hinweis schon angesprochen besteht das Blütenblatt aus 2 Kreisbögen. Verwenden Sie eine Schleife und `arc` um das Blatt zu zeichnen. Vergessen Sie nicht, die Funktion zu testen. \n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">6. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Nach dem ersten Kreisbogen muss die Schildkröte sich soweit drehen, dass sie mit dem nächsten Kreisbogen eine volle 180° Drehung vollzogen hat.\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">7. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Wenn Sie ein einzelnes Blütenblatt zeichnen können gilt es nun sich zu überlegen, wie man die Blütenblätter zu einer Blume zusammenfügen kann. Schreiben Sie den Kopf der Blumen Funktion und überlegen Sie, welche Informationen der Funktion übergeben werden müssen\n", " \n", " </div> \n", "</details>\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">8. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Da die Blumenfunktion Informationen an die Blütenblatt Funktion weitergeben muss, muss die Funktion die selben Parameter erhalten. Zusätzlich muss die Funktion noch gesagt bekommen, wie viele Blütenblätter gezeichnet werden sollen.\n", " \n", " </div> \n", "</details>\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">9. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Um die richtige Anzahl an Blütenblättern zu zeichnen müssen Sie die Funktion \"Blatt\" innerhalb der Funktion \"Blume\" entsprechend oft aufrufen, verwenden Sie dafür eine Schleife.\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">10. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Um die Blütenblätter gleichmäßig um den Mittelpunkt der Blume zu verteilen, muss der Winkel zwischen den Blättern so berechnet werden, dass nach Vollenden der Blume eine 360° Wendung vollzogen wurde. Überlegen Sie wie Sie dabei vorgehen müssen?\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">11. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", "\n", "Dafür muss der Winkel zwischen den Blättern 360/n betragen, wobei n für die Anzahl der Blätter steht.\n", "\n", " </div> \n", "</details>" ] }, { "cell_type": "code", "execution_count": null, "id": "0f0e7498", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "28f3436f-2525-413d-bd08-4efd20937720", "metadata": {}, "source": [ "\n", "\n", "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/jasoneppink/4964471335\" title=\"Spoiler Alert\"><img src=\"https://farm5.staticflickr.com/4110/4964471335_1f86a923f3_n.jpg\" width=\"320\" height=\"213\" alt=\"Spoiler Alert\"></a><script async src=\"//embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>\n", "\n", "(Quelle: Jason Eppink, Flickr)\n", "\n", "\n", "Hier ist eine weitere mögliche [Lösung](https://github.com/AllenDowney/ThinkPython2/blob/master/code/flower.py).\n" ] }, { "cell_type": "code", "execution_count": null, "id": "4cfea3b0", "metadata": { "tags": [ "remove-input" ] }, "outputs": [], "source": [ "import math\n", "import jupyturtle\n", "\n", "bob = jupyturtle.Turtle(delay=0.01)\n", "\n", "# damit diese Funktionen funktionieren, verwenden wir eine neue Version der Funktion arc() welche auch eine Schildröte t als Parameter übernimmt\n", "def arc(t, r, angle):\n", " '''zeichnet einen Kreisbogen in Abhängigkeit von Radius r und Winkel angle'''\n", " arc_length = 2 * math.pi * r * angle / 360\n", " n = int(arc_length / 3) + 1\n", " step_length = arc_length / n\n", " step_angle = angle / n\n", " \n", " for i in range(n):\n", " t.forward(step_length)\n", " t.left(step_angle)\n", "\n", "def blatt(t, r, angle):\n", " for i in range(2):\n", " arc(t,r, angle)\n", " t.left(180.0 - angle)\n", " \n", " \n", "def blume(t, r, angle, n):\n", " for i in range(n):\n", " blatt(t, r, angle)\n", " t.left(360 / n)\n", "\n", "blume(bob, 60.0, 60.0, 5)\n", "\n" ] }, { "cell_type": "markdown", "id": "60a71647-ccb1-42d2-8b1d-0098324f3dc9", "metadata": {}, "source": [ "### Aufgabe 7\n", "\n", "\n", "Lesen Sie etwas zu Spiralen auf [Wikipedia](https://de.wikipedia.org/wiki/Spirale). Schreiben Sie dann ein Programm, welches eine [Archimedische Spirale](https://de.wikipedia.org/wiki/Archimedische_Spirale) (oder eine andere) zeichnet.\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">1. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "In dieser Aufgabe, können Sie entscheiden, wie komplex Sie die Schnittstelle gestalten wollen. Sie müssen mindestens die Schildkröte übergeben, die die Spirale zeichnen soll, in maximaler Komplexität kann der Nutzer alle Variablen der Spirale anpassen. Hier wurde entschieden, dass der Nutzer lediglich die Schildkröte und die Anzahl der Spiralensegmente angeben darf, wenn Sie eine komplexere Lösung haben wollen, fügen Sie diese Parameter dem Funktionsaufruf hinzu.\n", " \n", " </div> \n", "</details>\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">2. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Die Formel für die Berechnung von Spiralen ist $r = a + b*\\theta$. Dabei können Sie einfach Ausgangswerte für a und b festlegen. Ein geeigneter Wert für a ist zum Beispiel 0.1 und für b 0.0002. Wenn ihre Funktion funktioniert, probieren Sie verschiedene Werte für a und b aus und sehen Sie sich an, wie die Spirale sich verändert. \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">3. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Weitere Parameter können entweder in der Schnittstelle oder aber wie in dem Beispiel unten in der Funktion festgelegt werden. Diese Parameter sind der Ausgangswinkel theta der Spirale und die Länge der einzelnen Spiralensegmente.\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">4. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Analog zum Kreisbogen wird auch die Spirale durch kurze gerade Segmente und Drehungen gezeichnet. Da das Zeichnen von Segmenten n-mal wiederholt wird, benötigen Sie eine for-Schleife.\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">5. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "In der Schleife muss für jedes Segment neu berechnet werden wie weit die Schildkröte sich drehen muss, da der Winkel nicht konstant bleibt. Dafür verwendet die Lösung: dtheta = 1 / (a + b * theta)\n", " \n", " </div> \n", "</details>\n", "\n", "\n", "<details>\n", " <summary type=\"button\" class=\"btn btn-primary\">6. Hinweis</summary>\n", " <div class=\"alert alert-info\" role=\"alert\">\n", " \n", "Da diese Formel theta als den Winkel des vorherigen Abschnitts verwendet, muss theta ebenfalls in jedem Schleifendurchlauf überschrieben werden - also addieren Sie dtheta auf theta auf. \n", " \n", " </div> \n", "</details>\n" ] }, { "cell_type": "markdown", "id": "a60ac5b7-7d70-4de8-9ffa-0f57f4cb54db", "metadata": {}, "source": [] }, { "cell_type": "markdown", "id": "a7790160-35b3-435f-a123-68ea72611b66", "metadata": {}, "source": [ "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/jasoneppink/4964471335\" title=\"Spoiler Alert\"><img src=\"https://farm5.staticflickr.com/4110/4964471335_1f86a923f3_n.jpg\" width=\"320\" height=\"213\" alt=\"Spoiler Alert\"></a><script async src=\"//embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>\n", "\n", "\n", "Eine [Lösung](http://thinkpython2.com/code/spiral.py)" ] }, { "cell_type": "code", "execution_count": null, "id": "77c2e7e1-7942-47ae-9e91-86057c6b5d1d", "metadata": {}, "outputs": [], "source": [ "import jupyturtle\n", "\n", "def spirale(t,n):\n", " '''In dieser Spirale bestimmt der Nutzer, wie viele Segmente gezeichnet werden, die Windung der Spirale ist\n", " in der Funktion festgelegt.\n", " '''\n", " length=3 \n", " a=0.1\n", " b=0.0002\n", " theta=0.0\n", " for i in range (n):\n", " t.forward(length)\n", " dtheta = 1 / (a + b * theta)\n", " t.left(dtheta)\n", " theta = theta + dtheta\n", "\n", "bob=jupyturtle.Turtle(delay=0.01) \n", "spirale(bob, 100)\n" ] }, { "cell_type": "markdown", "id": "df317b05-06a2-4623-ac12-767e9015f5ce", "metadata": { "jp-MarkdownHeadingCollapsed": true }, "source": [ "\n", "### Bonusaufgabe\n", "\n", "*(Hinweis: Dies ist eher eine Fleißaufgabe.)*\n", "\n", "Die Buchstaben des Alphabets sind aus ein paar grundlegenden Elementen zusammengesetzt wie z.B. vertikalen und horizontalen Linien und einigen Kurven. Entwerfen Sie ein Alphabet, welches mit einer kleinen Anzahl einfacher Elemente gezeichnet werden kann und schreiben Sie dann Funktionen zum Zeichnen der Buchstaben. \n", "\n", "Sie sollten eine Funktion für jeden Buchstaben schreiben, die Sie `draw_a`, `draw_b`, etc. nennen und diese Funktionen in eine Datei namens `letters.py` packen. Sie können [hier](http://thinkpython2.com/code/typewriter.py) eine \"Schildkrötenschreibmaschine\" herunterladen und damit ihren Code testen.\n", "\n", "\n", "Das könnte dann so aussehen:\n", "\n", "\n", "Sie finden eine Lösung für diese Aufgabe [hier](http://thinkpython2.com/code/letters.py); Diese benötigt auch [polygon.py](http://thinkpython2.com/code/polygon.py)." ] }, { "cell_type": "markdown", "id": "9d9f35d1", "metadata": {}, "source": [ "### Fragen Sie einen virtuellen Assistenten\n", "\n", "Es gibt in Python einige Module wie `jupyturtle`, die Version, die wir in diesem Kapitel benutzt haben, wurde für dieses Notebook angepasst.\n", "Wenn Sie einen virtuellen Assistenten um Hilfe fragen wird dieser daher nicht wissen, welches Modul verwendet werden soll.\n", "Aber wenn Sie ihm einige Beispiele geben, mit denen er arbeiten kann, wird er es vermutlich herausfinden können.\n", "Testen Sie beispielsweise diesen Prompt, um zu sehen ob er eine Funktion schreiben kann, die eine Spirale zeichnet:\n", "\n", "```\n", "Das folgende Programm verwendet ein Turtle-Grafik-Modul, um einen Kreis zu zeichnen:\n", "\n", "from jupyturtle import make_turtle, forward, left\n", "import math\n", "\n", "def polygon(n, length):\n", " angle = 360 / n\n", " for i in range(n):\n", " forward(length)\n", " left(angle)\n", " \n", "def circle(radius):\n", " circumference = 2 * math.pi * radius\n", " n = 30\n", " length = circumference / n\n", " polygon(n, length)\n", " \n", "make_turtle(delay=0)\n", "circle(30)\n", "\n", "Schreibe eine Funktion, die eine Spirale zeichnet.\n", "```" ] }, { "cell_type": "markdown", "id": "7beb2afe", "metadata": {}, "source": [ "Denken Sie daran, dass das Ergebnis möglicherweise Features verwendet, die wir noch nicht behandelt haben, und dass es Fehler beinhalten könnte.\n", "Kopieren Sie den Code des VA und versuchen Sie, ob sie diesen zum Laufen bringen können.\n", "Wenn Sie nicht Ihr gewünschtes Ergebnis erhalten, versuchen Sie, Ihren Prompt anzupassen." ] }, { "cell_type": "code", "execution_count": null, "id": "46d3151c", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "186c7fbc", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "979f5ad4", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "4e446922-a521-4589-9362-7320bd7452c6", "metadata": {}, "source": [ " Speichern Sie dieses Notebook, so dass Ihre Änderungen nicht verlorengehen (nicht auf einem Pool-Rechner). Rufen Sie dazu im Menü *File* den Punkt *Download as* → *Notebook* auf und nutzen Sie beispielsweise einen USB-Stick, E-Mail, Google Drive, Dropbox oder Ihre [HU-Box](https://box.hu-berlin.de/). \n" ] }, { "cell_type": "markdown", "id": "6f6f915e-ed5e-44d4-b81e-b2fdbb4260f6", "metadata": {}, "source": [ "\n", "Herzlichen Glückwunsch! Sie haben das 4. Kapitel geschafft!" ] }, { "cell_type": "markdown", "id": "a5940a55-5fb2-434f-bab9-5e42d03e2335", "metadata": {}, "source": [ "<img src=\"https://scm.cms.hu-berlin.de/ibi/python/-/raw/master/img/by-nc-sa.png\" alt=\"CC BY-NC-SA\" style=\"width: 150px;\"/>\n", "\n", "Der Text dieses Notebooks ist als freies Werk unter der Lizenz [CC BY-NC-SA 4.0 ](https://creativecommons.org/licenses/by-nc-sa/4.0/) verfügbar.\n", "Der Code dieses Notebooks ist als freies Werk unter der Lizenz [MIT License](https://mit-license.org/) verfügbar.\n", "\n", "Es handelt sich um übersetzte und leicht veränderte Notebooks von [Allen B. Downey](https://allendowney.com) aus [Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html).\n" ] } ], "metadata": { "celltoolbar": "Tags", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.2" } }, "nbformat": 4, "nbformat_minor": 5 }