{ "cells": [ { "cell_type": "markdown", "id": "cba04bar", "metadata": {}, "source": [ "\n", "**Other Ways to open this chapter:** \n", "[JupyterLite](https://python.porttack.com/jupyterlite-3db08322a4/notebooks/index.html?path=Chapter04-Functions-and-Interfaces.ipynb) | \n", "[Colab](https://colab.research.google.com/github/porttack/working-in-python/blob/v3/chapters/chap04.ipynb) | \n", "[Read Only](https://python.porttack.com/chap04.html?readonly) | \n", "Download\n", "\n", "" ] }, { "cell_type": "markdown", "id": "cba04pane", "metadata": {}, "source": [ "\n", "

Ignore this cell — used when running JupyterLite.

\n", "\n", "\n", "
\n", "\n", "
\n", "\n", "" ] }, { "cell_type": "code", "execution_count": null, "id": "df64b7da", "metadata": { "tags": [] }, "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/porttack/working-in-python/raw/v3/working_in_python.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 working_in_python\n", "\n", "%load_ext autoreload\n", "%autoreload 2\n", "\n", "working_in_python.check_for_update(\n", " \"jupyterlite-3db08322a4\", \"Chapter04-Functions-and-Interfaces.ipynb\"\n", ")" ] }, { "cell_type": "markdown", "id": "fbb4d5a2", "metadata": {}, "source": [ "# Functions and Interfaces\n", "\n", "This chapter introduces a module called `jupyturtle`, which lets you 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": [] }, "source": [ "## The jupyturtle module\n", "\n", "To use the `jupyturtle` module, we can import it like this." ] }, { "cell_type": "code", "execution_count": null, "id": "8f5a8a45", "metadata": {}, "outputs": [], "source": [ "import jupyturtle" ] }, { "cell_type": "markdown", "id": "8c801121", "metadata": {}, "source": [ "Now we can use the functions defined in the module, like `make_turtle` and `forward`." ] }, { "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` 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": null, "id": "234fde81", "metadata": {}, "outputs": [], "source": [ "from jupyturtle import make_turtle, forward" ] }, { "cell_type": "markdown", "id": "c1322d31", "metadata": {}, "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": null, "id": "1e768880", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "forward(100)" ] }, { "cell_type": "markdown", "id": "bd319754", "metadata": {}, "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": null, "id": "6d874b03", "metadata": {}, "outputs": [], "source": [ "from jupyturtle import left, right" ] }, { "cell_type": "markdown", "id": "0da2a311", "metadata": {}, "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": null, "id": "1bb57a0c", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "forward(50)\n", "left(90)\n", "forward(50)" ] }, { "cell_type": "markdown", "id": "cea2940f", "metadata": {}, "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": {}, "source": [ "## Making a square\n", "\n", "Here's one way to make a square." ] }, { "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": [ "Because this program repeats the same pair of lines four times, we can do the same thing more concisely with a `for` loop.\n", "" ] }, { "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": [] }, "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": 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": [ "Now we can call the function like this." ] }, { "cell_type": "code", "execution_count": null, "id": "193bbe5e", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "square()" ] }, { "cell_type": "markdown", "id": "da905fc6", "metadata": {}, "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": {}, "outputs": [], "source": [ "def square(length):\n", " for i in range(4):\n", " forward(length)\n", " left(90)" ] }, { "cell_type": "markdown", "id": "397fda4b", "metadata": {}, "source": [ "Now we can draw squares with different sizes." ] }, { "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": [ "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": {}, "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 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": {}, "outputs": [], "source": [ "make_turtle()\n", "polygon(7, 30)" ] }, { "cell_type": "markdown", "id": "dc0226db", "metadata": {}, "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": [] }, "outputs": [], "source": [ "make_turtle()\n", "polygon(n=7, length=30)" ] }, { "cell_type": "markdown", "id": "6aa28eba", "metadata": {}, "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": {}, "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": {}, "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` 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": {}, "outputs": [], "source": [ "make_turtle(delay=0.02)\n", "circle(30)" ] }, { "cell_type": "markdown", "id": "701f9cf8", "metadata": {}, "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.\n", "" ] }, { "cell_type": "markdown", "id": "c48f262c", "metadata": {}, "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": {}, "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` 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": {}, "outputs": [], "source": [ "def polygon(n, length):\n", " angle = 360.0 / n\n", " polyline(n, length, angle)" ] }, { "cell_type": "markdown", "id": "2714a59e", "metadata": {}, "source": [ "And we can use `polyline` to write `arc`." ] }, { "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` 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": {}, "outputs": [], "source": [ "def circle(radius):\n", " arc(radius, 360)" ] }, { "cell_type": "markdown", "id": "313a357c", "metadata": {}, "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": {}, "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 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": {}, "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": [] }, "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": [] }, "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": [ "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": {}, "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": {}, "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": {}, "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": [ "And here's the refactored version that uses `arc`." ] }, { "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": [ "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": [] }, "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": {}, "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": {}, "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": {}, "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", "\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": {}, "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": "f9144799", "metadata": {}, "source": [ "\n", "**See also:** the [full vocabulary glossary](https://python.porttack.com/alignment/ap-vocabulary-glossary.html) collects every term in this book, alphabetized, alongside the complete AP CSP exam vocabulary list.\n", "" ] }, { "cell_type": "markdown", "id": "0bfe2e19", "metadata": {}, "source": [ "## Exercises" ] }, { "cell_type": "code", "execution_count": null, "id": "9f94061e", "metadata": { "tags": [] }, "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": {}, "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": {}, "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": {}, "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": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "4b05078c", "metadata": { "tags": [] }, "source": [ "You can use the following code to test your function." ] }, { "cell_type": "code", "execution_count": null, "id": "1311ee08", "metadata": { "tags": [] }, "outputs": [], "source": [ "make_turtle()\n", "rectangle(80, 40)" ] }, { "cell_type": "markdown", "id": "8b8faaf6", "metadata": {}, "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": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "7917956b", "metadata": { "tags": [] }, "source": [ "You can use the following code to test your function." ] }, { "cell_type": "code", "execution_count": null, "id": "1d845de9", "metadata": { "tags": [] }, "outputs": [], "source": [ "make_turtle()\n", "rhombus(50, 60)" ] }, { "cell_type": "markdown", "id": "a9175a90", "metadata": {}, "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": {}, "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": [] }, "source": [ "You can use the following code to test your functions." ] }, { "cell_type": "code", "execution_count": null, "id": "c8dfebc9", "metadata": { "tags": [] }, "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": {}, "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": "markdown", "id": "extra-exercises-pie-optional-note", "metadata": {}, "source": [ "\n", "*This one is optional -- try it if you have time, but it isn't expected.*\n", "" ] }, { "cell_type": "code", "execution_count": null, "id": "8be6442e", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "be1b7ed8", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "8702c0ad", "metadata": { "tags": [] }, "source": [ "You can use the following code to test your functions." ] }, { "cell_type": "code", "execution_count": null, "id": "c519ca39", "metadata": { "tags": [] }, "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": [] }, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "9c78b76f", "metadata": {}, "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": "markdown", "id": "extra-exercises-flower-optional-note", "metadata": {}, "source": [ "\n", "*This one is optional -- try it if you have time, but it isn't expected.*\n", "" ] }, { "cell_type": "code", "execution_count": null, "id": "0f0e7498", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "6c0d0bff", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "8fe06dea", "metadata": { "tags": [] }, "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": [] }, "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": [] }, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "46d3151c", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "186c7fbc", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "3e4f5a6b", "metadata": {}, "source": [ "\n", "## Homework\n", "\n", "Do either Exercise 1 or Exercise 2, not both. They're interchangeable, so pick whichever appeals to you. Doing both is fine, but the second one earns no additional credit. Required work is five exercises (whichever of 1/2 you pick, plus Exercises 3-6), about 38 minutes total. The time check below must be filled in. The spiral at the end is extra credit, the only extra credit in this chapter, and it never substitutes for a required exercise.\n", "\n", "Note: the turtle-drawing exercises above (rectangle, rhombus, parallelogram, pie, flower) are practice. They aren't graded. This is the graded homework.\n", "" ] }, { "cell_type": "markdown", "id": "7c8d9e0f", "metadata": {}, "source": [ "\n", "### Exercise 1: draw your initials (do 1 or 2)\n", "\n", "Using `forward`, `left`, `right`, and `jump`, draw your first and last initials with the turtle. Build each letter as its own function. For example, if your initials are `A` and `T`, write `draw_a()` and `draw_t()`, then a function called `draw_initials()` that calls them with a `jump` in between so they don't run together.\n", "\n", "If one of your initials has a curve in it (like C, O, S, G, or R), you don't need a perfect curve. Approximate it with several short straight segments that turn a little each time, the same idea the chapter used to approximate a circle with a polygon.\n", "" ] }, { "cell_type": "code", "execution_count": null, "id": "extra-exercises-initials-solution", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "# Solution goes here" ] }, { "cell_type": "markdown", "id": "extra-exercises-house-heading", "metadata": {}, "source": [ "\n", "\n", "### Exercise 2: draw a house from parts (do 1 or 2)\n", "\n", "Using `forward`, `left`, `right`, and `jump`, write `draw_wall()`, `draw_roof()`, and `draw_door()` as three separate functions, then a function called `draw_house()` that calls all three, using `jump` to position each piece so they end up in the right places relative to each other.\n", "\n", "If you want to add a round window, you don't need a perfect curve. Approximate it with several short straight segments, the same idea the chapter used to approximate a circle with a polygon.\n", "" ] }, { "cell_type": "code", "execution_count": null, "id": "extra-exercises-house-solution", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "# Solution goes here" ] }, { "cell_type": "markdown", "id": "9b0c1d2e", "metadata": {}, "source": [ "\n", "### Exercise 3: docstrings\n", "\n", "Go back to the function you wrote for Exercise 1 or 2 (`draw_initials()` or `draw_house()`, whichever you chose) and add a docstring to it, following the chapter's `polyline` example: explain in your own words what the function does.\n", "\n", "Re-run that cell so the docstring takes effect, then run `help(draw_initials)` or `help(draw_house)` (whichever applies) and confirm your docstring appears in the output.\n", "" ] }, { "cell_type": "markdown", "id": "1a2b3c4e", "metadata": {}, "source": [ "\n", "### Exercise 4: pinwheel\n", "\n", "You'll need a `square` function that takes a `size` parameter, the same one from the chapter. Define it again here, then write a function called `pinwheel` that draws several overlapping squares, each rotated a little further than the last, without lifting the pen between them:\n", "\n", "```python\n", "def pinwheel(n_shapes, size):\n", " for i in range(n_shapes):\n", " square(size)\n", " left(360 / n_shapes)\n", "```\n", "\n", "Try a few different values of `n_shapes` (6, then 12, then 36) and see what patterns emerge. Nothing here is new syntax. The surprise is how much visual complexity comes from just repeating and rotating a shape you already know how to draw.\n", "" ] }, { "cell_type": "markdown", "id": "5d6e7f8a", "metadata": {}, "source": [ "\n", "### Exercise 5: interface vs. implementation\n", "\n", "The chapter shows two versions of `circle`:\n", "\n", "```python\n", "def circle(radius):\n", " circumference = 2 * math.pi * radius\n", " n = 30\n", " length = circumference / n\n", " polygon(n, length)\n", "```\n", "\n", "and\n", "\n", "```python\n", "def circle(radius):\n", " arc(radius, 360)\n", "```\n", "\n", "These have the same interface but different implementations. In your own words, what does that mean?\n", "\n", "Answer in the markdown cell below.\n", "" ] }, { "cell_type": "markdown", "id": "extra-exercises-interface-answer", "metadata": {}, "source": [ "*Type your answer here.*" ] }, { "cell_type": "markdown", "id": "3f4a5b6c", "metadata": {}, "source": [ "\n", "### Exercise 6: reflection\n", "\n", "The chapter's development plan centers on three moves: encapsulation (wrapping code in a function), generalization (adding parameters), and refactoring (reorganizing working code without changing what it does). Which of these did you find yourself doing naturally while working through this chapter's exercises, and which felt unfamiliar?\n", "\n", "Answer in the markdown cell below, the same way you did in chap01, chap02, and chap03.\n", "" ] }, { "cell_type": "markdown", "id": "extra-exercises-reflection-answer", "metadata": {}, "source": [ "*Type your answer here.*" ] }, { "cell_type": "markdown", "id": "extra-exercises-time-check-heading", "metadata": {}, "source": [ "\n", "\n", "### Time check\n", "\n", "This is graded on being filled in, not on the numbers. There's no right answer, and low numbers don't score better. If this chapter took you three hours, I need to know that. These are estimates: nobody expects you to have timed yourself, so a rough number is exactly what's wanted.\n", "" ] }, { "cell_type": "code", "execution_count": null, "id": "extra-exercises-time-check-code", "metadata": {}, "outputs": [], "source": [ "# Fill this in before you submit. Rough estimates are fine, and low numbers\n", "# don't score better. Don't count time on the extra-credit spiral below.\n", "# chapter_minutes -> reading the chapter and its practice exercises\n", "# extra_exercises_minutes -> the six numbered exercises above\n", "# longest -> which exercise took longest, e.g. 1\n", "chapter_minutes = 0\n", "extra_exercises_minutes = 0\n", "longest = 0\n", "\n", "from working_in_python import time_check\n", "time_check(chapter_minutes, extra_exercises_minutes, longest)" ] }, { "cell_type": "markdown", "id": "extra-exercises-spiral-heading", "metadata": {}, "source": [ "\n", "\n", "### Spiral (extra credit)\n", "\n", "Write a function called `spiral` that draws a spiral using `forward` and `left`, where each segment is slightly longer than the one before. The loop body is nearly identical to `polygon` from the chapter. The only difference is that the length changes each time through.\n", "\n", "Pick your own parameters (number of segments, starting length, growth per step, turn angle) and try a few combinations.\n", "\n", "This is extra credit. It does not replace any required exercise.\n", "" ] }, { "cell_type": "code", "execution_count": null, "id": "extra-exercises-spiral-solution", "metadata": {}, "outputs": [], "source": [ "make_turtle()\n", "# Solution goes here" ] }, { "cell_type": "markdown", "id": "extra-exercises-copy-heading", "metadata": {}, "source": [ "\n", "### Finished? Copy your work\n", "\n", "This isn't part of Exercise 6. It's a tool. Run the cell below to copy this notebook (including anything you've run) so you can paste it into a document.\n", "" ] }, { "cell_type": "code", "execution_count": null, "id": "extra-exercises-copy-button", "metadata": {}, "outputs": [], "source": [ "working_in_python.show_copy_notebook_button()" ] }, { "cell_type": "markdown", "id": "a7f4edf8", "metadata": { "tags": [] }, "source": [ "\n", "---\n", "\n", "**Working in Python** — modified by Eric Brown for a high school Computer Science Principles class. Source and modifications: [github.com/porttack/working-in-python](https://github.com/porttack/working-in-python)\n", "\n", "---\n", "\n", "\n", "[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/)\n", "\n", "\n", "---\n", "\n", "## Standards alignment\n", "\n", "**AP CSP:** [3.13 Developing Procedures](https://python.porttack.com/alignment/apcsp-standards-reference.html#T-3.13) (Big Idea 3, 30–35% of the exam) and [1.3 Program Design and Development](https://python.porttack.com/alignment/apcsp-standards-reference.html#T-1.3) (Big Idea 1, 10–13% — chapter 4 is this topic's only carrier in the book). Also [1.4 Identifying and Correcting Errors](https://python.porttack.com/alignment/apcsp-standards-reference.html#T-1.4) and [3.14 Libraries](https://python.porttack.com/alignment/apcsp-standards-reference.html#T-3.14), headers only.\n", "**California 9-12:** [9-12.AP.16](https://python.porttack.com/alignment/ca-cs-standards-reference.html#S-9-12.AP.16), [9-12.AP.17](https://python.porttack.com/alignment/ca-cs-standards-reference.html#S-9-12.AP.17), [9-12.AP.20](https://python.porttack.com/alignment/ca-cs-standards-reference.html#S-9-12.AP.20), [9-12.AP.22](https://python.porttack.com/alignment/ca-cs-standards-reference.html#S-9-12.AP.22)\n", "**CSTA 2026:** [HS-PRO-PD-12](https://python.porttack.com/alignment/csta2026-standards-reference.html#T-HS-PRO-PD-12), [HS-ALG-PS-02](https://python.porttack.com/alignment/csta2026-standards-reference.html#T-HS-ALG-PS-02). Also [HS-PRO-PD-13](https://python.porttack.com/alignment/csta2026-standards-reference.html#T-HS-PRO-PD-13) and [HS-PRO-RD-17](https://python.porttack.com/alignment/csta2026-standards-reference.html#T-HS-PRO-RD-17), headers only.\n", "**CA CTE (ICT):** [C4.9](https://python.porttack.com/alignment/ca-ict-anchor-standards-reference.html#T-C4.9) (Pathway C), [5.9](https://python.porttack.com/alignment/ca-ict-anchor-standards-reference.html#T-5.9), [5.10](https://python.porttack.com/alignment/ca-ict-anchor-standards-reference.html#T-5.10) (Anchor Standard 5, Problem Solving). Also [C4.11](https://python.porttack.com/alignment/ca-ict-anchor-standards-reference.html#T-C4.11) and [C5.6](https://python.porttack.com/alignment/ca-ict-anchor-standards-reference.html#T-C5.6), headers only.\n", "\n", "This chapter turns decomposition into a design habit: wrapping code in functions, generalizing them with parameters, and refactoring `circle` into `arc` and `polyline` once a better shape emerges. The development plan and docstrings make that design process explicit and documented — the clearest interface-versus-implementation example in the book so far, the same idea California, CSTA, and the ICT standards each name in their own words. Debugging returns as preconditions and postconditions, the same recurring thread as every other chapter.\n", "\n", "**Indexing:** this chapter's keyword arguments (`polygon(n=7, length=30)`) have no equivalent in the exam's pseudocode, which calls every procedure positionally.\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.10.11" } }, "nbformat": 4, "nbformat_minor": 5 }