diff --git a/chapters/chap04.ipynb b/chapters/chap04.ipynb new file mode 100644 index 000000000..e48cbb93e --- /dev/null +++ b/chapters/chap04.ipynb @@ -0,0 +1,1723 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1331faa1", + "metadata": { + "id": "1331faa1" + }, + "source": [ + "You can order print and ebook versions of *Think Python 3e* from\n", + "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n", + "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "df64b7da", + "metadata": { + "tags": [], + "id": "df64b7da", + "outputId": "f04ae431-0a54-4b7e-d6c0-8480356419f3", + "colab": { + "base_uri": "https://localhost:8080/" + } + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Downloaded thinkpython.py\n", + "Downloaded diagram.py\n", + "Downloaded jupyturtle.py\n" + ] + } + ], + "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');" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "320fc8bc", + "metadata": { + "tags": [], + "id": "320fc8bc", + "outputId": "2f67dfab-95e7-445a-a0ab-d4e20d122814", + "colab": { + "base_uri": "https://localhost:8080/" + } + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], + "source": [ + "import thinkpython\n", + "\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "markdown", + "id": "fbb4d5a2", + "metadata": { + "id": "fbb4d5a2" + }, + "source": [ + "# Functions and Interfaces\n", + "\n", + "This chapter introduces a module called `jupyturtle`, which allows you to create simple drawings by giving instructions to an imaginary turtle.\n", + "We will use this module to write functions that draw squares, polygons, and circles -- and to demonstrate **interface design**, which is a way of designing functions that work together." + ] + }, + { + "cell_type": "markdown", + "id": "0b0efa00", + "metadata": { + "tags": [], + "id": "0b0efa00" + }, + "source": [ + "## The jupyturtle module\n", + "\n", + "To use the `jupyturtle` module, we can import it like this." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8f5a8a45", + "metadata": { + "id": "8f5a8a45" + }, + "outputs": [], + "source": [ + "import jupyturtle" + ] + }, + { + "cell_type": "markdown", + "id": "8c801121", + "metadata": { + "id": "8c801121" + }, + "source": [ + "Now we can use the functions defined in the module, like `make_turtle` and `forward`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b3f255cd", + "metadata": { + "id": "b3f255cd", + "outputId": "c2ec8dd7-28a4-4ba2-ec36-202f6dafbf58", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 171 + } + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ] + }, + "metadata": {} + } + ], + "source": [ + "jupyturtle.make_turtle()\n", + "jupyturtle.forward(100)" + ] + }, + { + "cell_type": "markdown", + "id": "77a61cbb", + "metadata": { + "id": "77a61cbb" + }, + "source": [ + "`make_turtle` creates a **canvas**, which is a space on the screen where we can draw, and a turtle, which is represented by a circular shell and a triangular head.\n", + "The circle shows the location of the turtle and the triangle indicates the direction it is facing.\n", + "\n", + "`forward` moves the turtle a given distance in the direction it's facing, drawing a line segment along the way.\n", + "The distance is in arbitrary units -- the actual size depends on your computer's screen.\n", + "\n", + "We will use functions defined in the `jupyturtle` module many times, so it would be nice if we did not have to write the name of the module every time.\n", + "That's possible if we import the module like this." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "234fde81", + "metadata": { + "id": "234fde81" + }, + "outputs": [], + "source": [ + "from jupyturtle import make_turtle, forward" + ] + }, + { + "cell_type": "markdown", + "id": "c1322d31", + "metadata": { + "id": "c1322d31" + }, + "source": [ + "This version of the import statement imports `make_turtle` and `forward` from the `jupyturtle` module so we can call them like this." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "1e768880", + "metadata": { + "id": "1e768880", + "outputId": "eff17058-2757-428f-f5a3-967eb5d94c82", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 171 + } + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ] + }, + "metadata": {} + } + ], + "source": [ + "make_turtle()\n", + "forward(100)" + ] + }, + { + "cell_type": "markdown", + "id": "bd319754", + "metadata": { + "id": "bd319754" + }, + "source": [ + "`jupyturtle` provides two other functions we'll use, called `left` and `right`.\n", + "We'll import them like this." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "6d874b03", + "metadata": { + "id": "6d874b03" + }, + "outputs": [], + "source": [ + "from jupyturtle import left, right" + ] + }, + { + "cell_type": "markdown", + "id": "0da2a311", + "metadata": { + "id": "0da2a311" + }, + "source": [ + "`left` causes the turtle to turn left. It takes one argument, which is the angle of the turn in degrees.\n", + "For example, we can make a 90 degree left turn like this." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "1bb57a0c", + "metadata": { + "id": "1bb57a0c", + "outputId": "5661c0d1-c7df-4b44-d7f8-1cd935c40e96", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 171 + } + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ] + }, + "metadata": {} + } + ], + "source": [ + "make_turtle()\n", + "forward(50)\n", + "left(90)\n", + "forward(50)" + ] + }, + { + "cell_type": "markdown", + "id": "cea2940f", + "metadata": { + "id": "cea2940f" + }, + "source": [ + "This program moves the turtle east and then north, leaving two line segments behind.\n", + "Before you go on, see if you can modify the previous program to make a square." + ] + }, + { + "cell_type": "markdown", + "id": "e20ea96c", + "metadata": { + "id": "e20ea96c" + }, + "source": [ + "## Making a square\n", + "\n", + "Here's one way to make a square." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "9a9e455f", + "metadata": { + "id": "9a9e455f", + "outputId": "82291cd1-95a5-4189-9f3e-ab56f409a4e0", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 171 + } + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ] + }, + "metadata": {} + } + ], + "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": { + "id": "a7500957" + }, + "source": [ + "Because this program repeats the same pair of lines four times, we can do the same thing more concisely with a `for` loop." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cc27ad66", + "metadata": { + "id": "cc27ad66", + "outputId": "31f8b3b6-4e81-4682-9947-dc82d413777a", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 171 + } + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + "" + ] + }, + "metadata": {} + } + ], + "source": [ + "make_turtle()\n", + "for i in range(4):\n", + " forward(50)\n", + " left(90)" + ] + }, + { + "cell_type": "markdown", + "id": "c072ea41", + "metadata": { + "tags": [], + "id": "c072ea41" + }, + "source": [ + "## Encapsulation and generalization\n", + "\n", + "Let's take the square-drawing code from the previous section and put it in a function called `square`." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "ad5f1128", + "metadata": { + "id": "ad5f1128" + }, + "outputs": [], + "source": [ + "def square():\n", + " for i in range(4):\n", + " forward(50)\n", + " left(90)" + ] + }, + { + "cell_type": "markdown", + "id": "0789b5d9", + "metadata": { + "id": "0789b5d9" + }, + "source": [ + "Now we can call the function like this." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "193bbe5e", + "metadata": { + "id": "193bbe5e" + }, + "outputs": [], + "source": [ + "make_turtle()\n", + "square()" + ] + }, + { + "cell_type": "markdown", + "id": "da905fc6", + "metadata": { + "id": "da905fc6" + }, + "source": [ + "Wrapping a piece of code up in a function is called **encapsulation**.\n", + "One of the benefits of encapsulation is that it attaches a name to the code, which serves as a kind of documentation. Another advantage is that if you re-use the code, it is more concise to call a function twice than to copy and paste the body!\n", + "\n", + "In the current version, the size of the square is always `50`.\n", + "If we want to draw squares with different sizes, we can take the length of the sides as a parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "def8a5f1", + "metadata": { + "id": "def8a5f1" + }, + "outputs": [], + "source": [ + "def square(length):\n", + " for i in range(4):\n", + " forward(length)\n", + " left(90)" + ] + }, + { + "cell_type": "markdown", + "id": "397fda4b", + "metadata": { + "id": "397fda4b" + }, + "source": [ + "Now we can draw squares with different sizes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b283e795", + "metadata": { + "id": "b283e795" + }, + "outputs": [], + "source": [ + "make_turtle()\n", + "square(30)\n", + "square(60)" + ] + }, + { + "cell_type": "markdown", + "id": "5a46bf64", + "metadata": { + "id": "5a46bf64" + }, + "source": [ + "Adding a parameter to a function is called **generalization** because it makes the function more general: with the previous version, the square is always the same size; with this version it can be any size.\n", + "\n", + "If we add another parameter, we can make it even more general.\n", + "The following function draws regular polygons with a given number of sides." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "171974ed", + "metadata": { + "id": "171974ed" + }, + "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": { + "id": "286d3c77" + }, + "source": [ + "In a regular polygon with `n` sides, the angle between adjacent sides is `360 / n` degrees.\n", + "\n", + "The following example draws a `7`-sided polygon with side length `30`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71f7d9d2", + "metadata": { + "id": "71f7d9d2" + }, + "outputs": [], + "source": [ + "make_turtle()\n", + "polygon(7, 30)" + ] + }, + { + "cell_type": "markdown", + "id": "dc0226db", + "metadata": { + "id": "dc0226db" + }, + "source": [ + "When a function has more than a few numeric arguments, it is easy to forget what they are, or what order they should be in.\n", + "It can be a good idea to include the names of the parameters in the argument list." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ff2a5f4", + "metadata": { + "tags": [], + "id": "8ff2a5f4" + }, + "outputs": [], + "source": [ + "make_turtle()\n", + "polygon(n=7, length=30)" + ] + }, + { + "cell_type": "markdown", + "id": "6aa28eba", + "metadata": { + "id": "6aa28eba" + }, + "source": [ + "These are sometimes called \"named arguments\" because they include the parameter names.\n", + "But in Python they are more often called **keyword arguments** (not to be confused with Python keywords like `for` and `def`).\n", + "\n", + "This use of the assignment operator, `=`, is a reminder about how arguments and parameters work -- when you call a function, the arguments are assigned to the parameters." + ] + }, + { + "cell_type": "markdown", + "id": "b10184b4", + "metadata": { + "id": "b10184b4" + }, + "source": [ + "## Approximating a circle\n", + "\n", + "Now suppose we want to draw a circle.\n", + "We can do that, approximately, by drawing a polygon with a large number of sides, so each side is small enough that it's hard to see.\n", + "Here is a function that uses `polygon` to draw a `30`-sided polygon that approximates a circle." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f2a5f28", + "metadata": { + "id": "7f2a5f28" + }, + "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": { + "id": "39023314" + }, + "source": [ + "`circle` takes the radius of the the circle as a parameter.\n", + "It computes `circumference`, which is the circumference of a circle with the given radius.\n", + "`n` is the number of sides, so `circumference / n` is the length of each side.\n", + "\n", + "This function might take a long time to run.\n", + "We can speed it up by calling `make_turtle` with a keyword argument called `delay` that sets the time, in seconds, the turtle waits after each step.\n", + "The default value is `0.2` seconds -- if we set it to `0.02` it runs about 10 times faster." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75258056", + "metadata": { + "id": "75258056" + }, + "outputs": [], + "source": [ + "make_turtle(delay=0.02)\n", + "circle(30)" + ] + }, + { + "cell_type": "markdown", + "id": "701f9cf8", + "metadata": { + "id": "701f9cf8" + }, + "source": [ + "A limitation of this solution is that `n` is a constant, which means\n", + "that for very big circles, the sides are too long, and for small\n", + "circles, we waste time drawing very short sides.\n", + "One option is to generalize the function by taking `n` as a parameter.\n", + "But let's keep it simple for now." + ] + }, + { + "cell_type": "markdown", + "id": "c48f262c", + "metadata": { + "id": "c48f262c" + }, + "source": [ + "## Refactoring\n", + "\n", + "Now let's write a more general version of `circle`, called `arc`, that takes a second parameter, `angle`, and draws an arc of a circle that spans the given angle.\n", + "For example, if `angle` is `360` degrees, it draws a complete circle. If `angle` is `180` degrees, it draws a half circle.\n", + "\n", + "To write `circle`, we were able to reuse `polygon`, because a many-sided polygon is a good approximation of a circle.\n", + "But we can't use `polygon` to write `arc`.\n", + "\n", + "Instead, we'll create the more general version of `polygon`, called `polyline`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "381edd23", + "metadata": { + "id": "381edd23" + }, + "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": { + "id": "c2b2503e" + }, + "source": [ + "`polyline` takes as parameters the number of line segments to draw, `n`, the length of the segments, `length`, and the angle between them, `angle`.\n", + "\n", + "Now we can rewrite `polygon` to use `polyline`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f4eecc0", + "metadata": { + "id": "2f4eecc0" + }, + "outputs": [], + "source": [ + "def polygon(n, length):\n", + " angle = 360.0 / n\n", + " polyline(n, length, angle)" + ] + }, + { + "cell_type": "markdown", + "id": "2714a59e", + "metadata": { + "id": "2714a59e" + }, + "source": [ + "And we can use `polyline` to write `arc`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "539466f6", + "metadata": { + "id": "539466f6" + }, + "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": { + "id": "3c18773c" + }, + "source": [ + "`arc` is similar to `circle`, except that it computes `arc_length`, which is a fraction of the circumference of a circle.\n", + "\n", + "Finally, we can rewrite `circle` to use `arc`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e09f456", + "metadata": { + "id": "8e09f456" + }, + "outputs": [], + "source": [ + "def circle(radius):\n", + " arc(radius, 360)" + ] + }, + { + "cell_type": "markdown", + "id": "313a357c", + "metadata": { + "id": "313a357c" + }, + "source": [ + "To check that these functions work as expected, we'll use them to draw something like a snail.\n", + "With `delay=0`, the turtle runs as fast as possible." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80d6eadd", + "metadata": { + "id": "80d6eadd" + }, + "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": { + "id": "a34da3d8" + }, + "source": [ + "In this example, we started with working code and reorganized it with different functions.\n", + "Changes like this, which improve the code without changing its behavior, are called **refactoring**.\n", + "\n", + "If we had planned ahead, we might have written `polyline` first and avoided refactoring, but often you don't know enough at the beginning of a project to design all the functions.\n", + "Once you start coding, you understand the problem better.\n", + "Sometimes refactoring is a sign that you have learned something." + ] + }, + { + "cell_type": "markdown", + "id": "d18c9d16", + "metadata": { + "id": "d18c9d16" + }, + "source": [ + "## Stack diagram\n", + "\n", + "When we call `circle`, it calls `arc`, which calls `polyline`.\n", + "We can use a stack diagram to show this sequence of function calls and the parameters for each one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1571ee71", + "metadata": { + "tags": [], + "id": "1571ee71" + }, + "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": [], + "id": "f4e37360" + }, + "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": { + "id": "3160bba1" + }, + "source": [ + "Notice that the value of `angle` in `polyline` is different from the value of `angle` in `arc`.\n", + "Parameters are local, which means you can use the same parameter name in different functions; it's a different variable in each function, and it can refer to a different value." + ] + }, + { + "cell_type": "markdown", + "id": "c23552d3", + "metadata": { + "id": "c23552d3" + }, + "source": [ + "## A development plan\n", + "\n", + "A **development plan** is a process for writing programs.\n", + "The process we used in this chapter is \"encapsulation and generalization\".\n", + "The steps of this process are:\n", + "\n", + "1. Start by writing a small program with no function definitions.\n", + "\n", + "2. Once you get the program working, identify a coherent piece of it,\n", + " encapsulate the piece in a function and give it a name.\n", + "\n", + "3. Generalize the function by adding appropriate parameters.\n", + "\n", + "4. Repeat Steps 1 to 3 until you have a set of working functions.\n", + "\n", + "5. Look for opportunities to improve the program by refactoring. For\n", + " example, if you have similar code in several places, consider\n", + " factoring it into an appropriately general function.\n", + "\n", + "This process has some drawbacks -- we will see alternatives later -- but it can be useful if you don't know ahead of time how to divide the program into functions.\n", + "This approach lets you design as you go along." + ] + }, + { + "cell_type": "markdown", + "id": "a3b6b83d", + "metadata": { + "id": "a3b6b83d" + }, + "source": [ + "The design of a function has two parts:\n", + "\n", + "* The **interface** is how the function is used, including its name, the parameters it takes and what the function is supposed to do.\n", + "\n", + "* The **implementation** is how the function does what it's supposed to do.\n", + "\n", + "For example, here's the first version of `circle` we wrote, which uses `polygon`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "baf964ba", + "metadata": { + "id": "baf964ba" + }, + "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": { + "id": "5d3d2e79" + }, + "source": [ + "And here's the refactored version that uses `arc`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2e006d5", + "metadata": { + "id": "e2e006d5" + }, + "outputs": [], + "source": [ + "def circle(radius):\n", + " arc(radius, 360)" + ] + }, + { + "cell_type": "markdown", + "id": "b726f72c", + "metadata": { + "id": "b726f72c" + }, + "source": [ + "These two functions have the same interface -- they take the same parameters and do the same thing -- but they have different implementations." + ] + }, + { + "cell_type": "markdown", + "id": "3e3bae20", + "metadata": { + "tags": [], + "id": "3e3bae20" + }, + "source": [ + "## Docstrings\n", + "\n", + "A **docstring** is a string at the beginning of a function that explains the interface (\"doc\" is short for \"documentation\").\n", + "Here is an example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b68f3682", + "metadata": { + "id": "b68f3682" + }, + "outputs": [], + "source": [ + "def polyline(n, length, angle):\n", + " \"\"\"Draws line segments with the given length and angle between them.\n", + "\n", + " n: integer number of line segments\n", + " length: length of the line segments\n", + " angle: angle between segments (in degrees)\n", + " \"\"\"\n", + " for i in range(n):\n", + " forward(length)\n", + " left(angle)" + ] + }, + { + "cell_type": "markdown", + "id": "55b60cbc", + "metadata": { + "id": "55b60cbc" + }, + "source": [ + "By convention, docstrings are triple-quoted strings, also known as **multiline strings** because the triple quotes allow the string to span more than one line.\n", + "\n", + "A docstring should:\n", + "\n", + "* Explain concisely what the function does, without getting into the details of how it works,\n", + "\n", + "* Explain what effect each parameter has on the behavior of the function, and\n", + "\n", + "* Indicate what type each parameter should be, if it is not obvious.\n", + "\n", + "Writing this kind of documentation is an important part of interface design.\n", + "A well-designed interface should be simple to explain; if you have a hard time explaining one of your functions, maybe the interface could be improved." + ] + }, + { + "cell_type": "markdown", + "id": "f1115940", + "metadata": { + "id": "f1115940" + }, + "source": [ + "## Debugging\n", + "\n", + "An interface is like a contract between a function and a caller. The\n", + "caller agrees to provide certain arguments and the function agrees to\n", + "do certain work.\n", + "\n", + "For example, `polyline` requires three arguments: `n` has to be an integer; `length` should be a positive number; and `angle` has to be a number, which is understood to be in degrees.\n", + "\n", + "These requirements are called **preconditions** because they are supposed to be true before the function starts executing. Conversely, conditions at the end of the function are **postconditions**.\n", + "Postconditions include the intended effect of the function (like drawing line segments) and any side effects (like moving the turtle or making other changes).\n", + "\n", + "Preconditions are the responsibility of the caller. If the caller violates a precondition and the function doesn't work correctly, the bug is in the caller, not the function.\n", + "\n", + "If the preconditions are satisfied and the postconditions are not, the bug is in the function. If your pre- and postconditions are clear, they can help with debugging." + ] + }, + { + "cell_type": "markdown", + "id": "a4d33a70", + "metadata": { + "id": "a4d33a70" + }, + "source": [ + "## Glossary\n", + "\n", + "**interface design:**\n", + "A process for designing the interface of a function, which includes the parameters it should take.\n", + "\n", + "**canvas:**\n", + "A window used to display graphical elements including lines, circles, rectangles, and other shapes.\n", + "\n", + "**encapsulation:**\n", + " The process of transforming a sequence of statements into a function definition.\n", + "\n", + "**generalization:**\n", + " The process of replacing something unnecessarily specific (like a number) with something appropriately general (like a variable or parameter).\n", + "\n", + "**keyword argument:**\n", + "An argument that includes the name of the parameter.\n", + "\n", + "**refactoring:**\n", + " The process of modifying a working program to improve function interfaces and other qualities of the code.\n", + "\n", + "**development plan:**\n", + "A process for writing programs.\n", + "\n", + "**docstring:**\n", + " A string that appears at the top of a function definition to document the function's interface.\n", + "\n", + "**multiline string:**\n", + "A string enclosed in triple quotes that can span more than one line of a program.\n", + "\n", + "**precondition:**\n", + " A requirement that should be satisfied by the caller before a function starts.\n", + "\n", + "**postcondition:**\n", + " A requirement that should be satisfied by the function before it ends." + ] + }, + { + "cell_type": "markdown", + "id": "0bfe2e19", + "metadata": { + "id": "0bfe2e19" + }, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f94061e", + "metadata": { + "tags": [], + "id": "9f94061e" + }, + "outputs": [], + "source": [ + "# This cell tells Jupyter to provide detailed debugging information\n", + "# when a runtime error occurs. Run it before working on the exercises.\n", + "\n", + "%xmode Verbose" + ] + }, + { + "cell_type": "markdown", + "id": "50ed5c38", + "metadata": { + "id": "50ed5c38" + }, + "source": [ + "For the exercises below, there are a few more turtle functions you might want to use.\n", + "\n", + "* `penup` lifts the turtle's imaginary pen so it doesn't leave a trail when it moves.\n", + "\n", + "* `pendown` puts the pen back down.\n", + "\n", + "The following function uses `penup` and `pendown` to move the turtle without leaving a trail." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6f9a0106", + "metadata": { + "id": "6f9a0106" + }, + "outputs": [], + "source": [ + "from jupyturtle import penup, pendown\n", + "\n", + "def jump(length):\n", + " \"\"\"Move forward length units without leaving a trail.\n", + "\n", + " Postcondition: Leaves the pen down.\n", + " \"\"\"\n", + " penup()\n", + " forward(length)\n", + " pendown()" + ] + }, + { + "cell_type": "markdown", + "id": "c78c1e17", + "metadata": { + "id": "c78c1e17" + }, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `rectangle` that draws a rectangle with given side lengths.\n", + "For example, here's a rectangle that's `80` units wide and `40` units tall." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c54ba660", + "metadata": { + "id": "c54ba660" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "id": "4b05078c", + "metadata": { + "tags": [], + "id": "4b05078c" + }, + "source": [ + "You can use the following code to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1311ee08", + "metadata": { + "tags": [], + "id": "1311ee08" + }, + "outputs": [], + "source": [ + "make_turtle()\n", + "rectangle(80, 40)" + ] + }, + { + "cell_type": "markdown", + "id": "8b8faaf6", + "metadata": { + "id": "8b8faaf6" + }, + "source": [ + "### Exercise\n", + "\n", + "Write a function called `rhombus` that draws a rhombus with a given side length and a given interior angle. For example, here's a rhombus with side length `50` and an interior angle of `60` degrees." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3db6f106", + "metadata": { + "id": "3db6f106" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "id": "7917956b", + "metadata": { + "tags": [], + "id": "7917956b" + }, + "source": [ + "You can use the following code to test your function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d845de9", + "metadata": { + "tags": [], + "id": "1d845de9" + }, + "outputs": [], + "source": [ + "make_turtle()\n", + "rhombus(50, 60)" + ] + }, + { + "cell_type": "markdown", + "id": "a9175a90", + "metadata": { + "id": "a9175a90" + }, + "source": [ + "### Exercise\n", + "\n", + "Now write a more general function called `parallelogram` that draws a quadrilateral with parallel sides. Then rewrite `rectangle` and `rhombus` to use `parallelogram`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "895005cb", + "metadata": { + "id": "895005cb" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e7d34b0", + "metadata": { + "id": "7e7d34b0" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "481396f9", + "metadata": { + "id": "481396f9" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "id": "c03bd4a2", + "metadata": { + "tags": [], + "id": "c03bd4a2" + }, + "source": [ + "You can use the following code to test your functions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8dfebc9", + "metadata": { + "tags": [], + "id": "c8dfebc9" + }, + "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": "991ab59d", + "metadata": { + "id": "991ab59d" + }, + "source": [ + "### Exercise\n", + "\n", + "Write an appropriately general set of functions that can draw shapes like this.\n", + "\n", + "![](https://github.com/AllenDowney/ThinkPython/raw/v3/jupyturtle_pie.png)\n", + "\n", + "Hint: Write a function called `triangle` that draws one triangular segment, and then a function called `draw_pie` that uses `triangle`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8be6442e", + "metadata": { + "id": "8be6442e" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be1b7ed8", + "metadata": { + "id": "be1b7ed8" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "id": "8702c0ad", + "metadata": { + "tags": [], + "id": "8702c0ad" + }, + "source": [ + "You can use the following code to test your functions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c519ca39", + "metadata": { + "tags": [], + "id": "c519ca39" + }, + "outputs": [], + "source": [ + "turtle = make_turtle(delay=0)\n", + "jump(-80)\n", + "\n", + "size = 40\n", + "draw_pie(5, size)\n", + "jump(2*size)\n", + "draw_pie(6, size)\n", + "jump(2*size)\n", + "draw_pie(7, size)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89ce198a", + "metadata": { + "tags": [], + "id": "89ce198a" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "id": "9c78b76f", + "metadata": { + "id": "9c78b76f" + }, + "source": [ + "### Exercise\n", + "\n", + "Write an appropriately general set of functions that can draw flowers like this.\n", + "\n", + "![](https://github.com/AllenDowney/ThinkPython/raw/v3/jupyturtle_flower.png)\n", + "\n", + "Hint: Use `arc` to write a function called `petal` that draws one flower petal." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0f0e7498", + "metadata": { + "id": "0f0e7498" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c0d0bff", + "metadata": { + "id": "6c0d0bff" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "id": "8fe06dea", + "metadata": { + "tags": [], + "id": "8fe06dea" + }, + "source": [ + "You can use the following code to test your functions.\n", + "\n", + "Because the solution draws a lot of small line segments, it tends to slow down as it runs.\n", + "To avoid that, you can add the keyword argument `auto_render=False` to avoid drawing after every step, and then call the `render` function at the end to show the result.\n", + "\n", + "While you are debugging, you might want to remove `auto_render=False`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "04193da5", + "metadata": { + "tags": [], + "id": "04193da5" + }, + "outputs": [], + "source": [ + "from jupyturtle import render\n", + "\n", + "turtle = make_turtle(auto_render=False)\n", + "\n", + "jump(-60)\n", + "n = 7\n", + "radius = 60\n", + "angle = 60\n", + "flower(n, radius, angle)\n", + "\n", + "jump(120)\n", + "n = 9\n", + "radius = 40\n", + "angle = 85\n", + "flower(n, radius, angle)\n", + "\n", + "render()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4cfea3b0", + "metadata": { + "tags": [], + "id": "4cfea3b0" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "id": "9d9f35d1", + "metadata": { + "id": "9d9f35d1" + }, + "source": [ + "### Ask a virtual assistant\n", + "\n", + "There are several modules like `jupyturtle` in Python, and the one we used in this chapter has been customized for this book.\n", + "So if you ask a virtual assistant for help, it won't know which module to use.\n", + "But if you give it a few examples to work with, it can probably figure it out.\n", + "For example, try this prompt and see if it can write a function that draws a spiral:\n", + "\n", + "```\n", + "The following program uses a turtle graphics module to draw a circle:\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", + "Write a function that draws a spiral.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "7beb2afe", + "metadata": { + "id": "7beb2afe" + }, + "source": [ + "Keep in mind that the result might use features we have not seen yet, and it might have errors.\n", + "Copy the code from the VA and see if you can get it working.\n", + "If you didn't get what you wanted, try modifying the prompt.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46d3151c", + "metadata": { + "id": "46d3151c" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "186c7fbc", + "metadata": { + "id": "186c7fbc" + }, + "outputs": [], + "source": [ + "# Solution goes here" + ] + }, + { + "cell_type": "markdown", + "id": "a7f4edf8", + "metadata": { + "tags": [], + "id": "a7f4edf8" + }, + "source": [ + "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n", + "\n", + "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n", + "\n", + "Code license: [MIT License](https://mit-license.org/)\n", + "\n", + "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)" + ] + } + ], + "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.10.11" + }, + "colab": { + "provenance": [] + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file