{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "l06-c000",
   "metadata": {},
   "source": [
    "# Lecture 6 - Functions\n",
    "\n",
    "**Authors:** Fiona McNeill, Matteo Degiacomi, Rayo Verweij, and Nigel Topham\n",
    "\n",
    "## Introduction\n",
    "\n",
    "So far, we've seen several functions built into Python. In this lecture, we will write functions of our own! A function lets us give a name to a piece of code, use it with different inputs, and reuse its result elsewhere in a program.\n",
    "\n",
    "### Learning outcomes\n",
    "\n",
    "In this notebook, we'll discuss:\n",
    "\n",
    "- how the IPO pattern is present in Python's built-in functions;\n",
    "- defining and calling functions with parameters and return values;\n",
    "- supplying positional and keyword arguments, and choosing default values;\n",
    "- documenting functions with docstrings; and\n",
    "- understanding `None` and variable scope. \n",
    "\n",
    "### Using the notebook\n",
    "\n",
    "Remember, this notebook is _yours_! You can edit each cell, for example by adding new examples or changing values to see what happens. Feel free to experiment!\n",
    "\n",
    "A common approach for learning how to read and write code in a structured way is called **PRIMM**:\n",
    "1. **Predict**: before running the code, look at it - do you understand each line? What do you think will happen?\n",
    "2. **Run**: now run the code and see what output you get.\n",
    "3. **Investigate**: did it go as you thought it would? If not, can you figure out why?\n",
    "4. **Modify**: now change the code and once again try to predict the output. If you got it wrong the first time, did you get it right now?\n",
    "5. **Make**: finally, try to write your own program from scratch using the skills you learned.\n",
    "\n",
    "You can apply steps 1-4 to each code cell in the notebook and many will have prediction prompts to help you get started. In addition, there will be challenges that ask you to put everything together yourself and execute step 5. There will often be multiple ways to solve these, but each challenge has a potential answer listed.\n",
    "\n",
    "### Navigating the notebook\n",
    "\n",
    "A Jupyter notebook can be operated with your keyboard. Here is a **cheat sheet**:\n",
    "\n",
    "- To run the selected cell and move to the next cell, press <kbd>&#x21E7; Shift</kbd> + <kbd>&#x23ce; Enter</kbd>.\n",
    "- To run the selected cell and keep focus there, press <kbd>⌃ Ctrl</kbd> + <kbd>&#x23ce; Enter</kbd>.\n",
    "- To create a cell under the one currently highlighted, press <kbd>B</kbd>;\n",
    "- To delete the currently highlighted cell, press <kbd>D</kbd> twice (be careful with this one!) or press <kbd>X</kbd> to cut it;\n",
    "- To get help for a specific function, place the cursor within the function's brackets, hold <kbd>&#x21E7; Shift</kbd>, and press <kbd>&#x21E5; Tab</kbd>.\n",
    "\n",
    "Watch out: **code cells remember what happened in cells before**. So, especially in the more complicated notebooks later down the line, make sure to always run **every** cell from top to bottom, as one might rely on a piece of code that came before it! If your results become confusing, use **Kernel > Restart Kernel** and then rerun all the completed cells in order.\n",
    "\n",
    "### Further reading and practice\n",
    "\n",
    "- Official Python Tutorial: [Defining functions](https://docs.python.org/3/tutorial/controlflow.html#defining-functions), [Default argument values](https://docs.python.org/3/tutorial/controlflow.html#default-argument-values), [Keyword arguments](https://docs.python.org/3/tutorial/controlflow.html#keyword-arguments), and [Documentation strings](https://docs.python.org/3/tutorial/controlflow.html#documentation-strings). The sections on special parameters and arbitrary argument lists are beyond this lecture.\n",
    "- W3Schools: [Functions](https://www.w3schools.com/python/python_functions.asp) and [Function arguments](https://www.w3schools.com/python/python_arguments.asp).\n",
    "- Helsinki Python MOOC: [Defining functions](https://programming-26.mooc.fi/part-3/4-defining-functions) and [More functions](https://programming-26.mooc.fi/part-4/2-more-functions).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb7febdb",
   "metadata": {},
   "source": [
    "## 1. Functions and arguments"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c001",
   "metadata": {},
   "source": [
    "In Lecture 1, we discussed the IPO pattern of programs:\n",
    "\n",
    "1. **Input:** obtain information from the user;\n",
    "2. **Process:** transform or combine that information, often by implementing some kind of algorithm;\n",
    "3. **Output:** communicate the result back to the user.\n",
    "\n",
    "While this pattern is often true of programs as a whole, it also occurs *within* programs.\n",
    "\n",
    "**Functions** are reusable pieces of code that have their own IPO cycle within your program.\n",
    "\n",
    "We have seen several of Python's built-in functions so far:\n",
    "\n",
    "| Function | Input | Process | Output |\n",
    "| -------- | ----- | ------- | ------ |\n",
    "| `print()` | Any | Sends its input to the terminal for display | None (in the program itself) |\n",
    "| `input()` | A prompt | Displays the prompt in the terminal and gets resulting terminal input | A string with terminal input |\n",
    "| `len()` | A string or collection | Counts its length | The length of the input as an integer |\n",
    "| `type()` | Any | Determines the input's type | The input's type in the format `<class 'type'>` |\n",
    "\n",
    "...and so on.\n",
    "\n",
    "Python has quite a few [built-in functions](https://docs.python.org/3/builtins/functions.html): for example, when working with a collection, it provides `min()` and `max()` to find its smallest and largest values, and `sum()` to add numeric values together.\n",
    "\n",
    "**Predict:** Work out each result before running the cell. Which two function calls are combined to calculate the mean?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c002",
   "metadata": {},
   "outputs": [],
   "source": [
    "scores = [68, 72, 75, 80]\n",
    "print(len(scores))\n",
    "print(min(scores))\n",
    "print(max(scores))\n",
    "print(sum(scores))\n",
    "print(sum(scores) / len(scores))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c003",
   "metadata": {},
   "source": [
    "You use (or **call**) a function by adding parentheses to the end of its name. Any input provided inside of a function call's parentheses is called an **argument**. For example, `scores` is the argument in `sum(scores)`. A function can have multiple arguments, separated by commas, as you've seen before:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f4f277a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Two arguments, printed on screen with a space in between when calling print()\n",
    "print(\"hello\", \"world\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c004",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: summarising measurements</b>\n",
    "\n",
    "For the following list of measurements, print the count, range (the difference between the maximum and minimum value), and mean.\n",
    "\n",
    "Then, as a comment, note _how many_ function arguments are present in your entire program.\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c005",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "measurements = [12.5, 10.0, 14.5, 13.0]\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c006",
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "source": [
    "<details>\n",
    "<summary><b>Click for the solution</b></summary>\n",
    "\n",
    "Your number of arguments might slightly vary depending on how you wrote your program. For example, if you formatted your print statements using f-strings, each one will have had only one argument rather than two.\n",
    "\n",
    "```python\n",
    "measurements = [12.5, 10.0, 14.5, 13.0]\n",
    "print(\"Count:\", len(measurements))\n",
    "print(\"Range:\", max(measurements) - min(measurements))\n",
    "print(\"Mean:\", sum(measurements) / len(measurements))\n",
    "\n",
    "# This program contains 11 arguments: one each for len(), max(), min(), sum(), and len() again; and then two for each print statement.\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ef7f950",
   "metadata": {},
   "source": [
    "## 2. Defining our own functions"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c007",
   "metadata": {},
   "source": [
    "We do not need to be limited to Python's built-in functions, however. We can define our own!\n",
    "\n",
    "To create our own function, we use the `def` keyword, followed by a name, parentheses, and a colon. Then, just like with conditions and loops, the function's _body_ is indented.\n",
    "\n",
    "Defining a function does not automatically run it; it simply makes it available to be called later, like the built-in functions we've seen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c008",
   "metadata": {},
   "outputs": [],
   "source": [
    "def show_welcome():\n",
    "    print(\"Welcome to the notebook\")\n",
    "    print(\"Try changing an example\")\n",
    "\n",
    "print(\"Before the call\")\n",
    "show_welcome()\n",
    "print(\"After the call\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c009",
   "metadata": {},
   "source": [
    "A function's input is defined within the parentheses in the head. Each piece of input is called a **parameter**.\n",
    "\n",
    "_Arguments_, as seen above, are then the specific values that we supply to parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c010",
   "metadata": {},
   "outputs": [],
   "source": [
    "# A function with one parameter, name\n",
    "def greet(name):\n",
    "    print(f\"Hello, {name}\")\n",
    "\n",
    "# Mina and Ravi are both possible arguments for the parameter name\n",
    "greet(\"Mina\")\n",
    "greet(\"Ravi\")\n",
    "\n",
    "# A function with two parameters, price and quantity\n",
    "def show_total(price, quantity):\n",
    "    print(f\"Total: £{price * quantity:.2f}\")\n",
    "\n",
    "# 2.5 and 3 are the arguments for the price and quantity parameters, respectively\n",
    "show_total(2.5, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c011",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: area of a rectangle</b>\n",
    "\n",
    "Define a function named `area` that takes in a `width` and `height` of a rectangle and prints both as well as its area.\n",
    "\n",
    "Call it at least twice with different pairs of arguments.\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c012",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "# Your code here!\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c013",
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "source": [
    "<details>\n",
    "<summary><b>Click for the solution</b></summary>\n",
    "\n",
    "```python\n",
    "def area(width, height):\n",
    "    print(f\"{width} × {height} has area {width * height}\")\n",
    "\n",
    "area(3, 4)\n",
    "area(2.5, 8)\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56a92af2",
   "metadata": {},
   "source": [
    "## 3. Returning values"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c014",
   "metadata": {},
   "source": [
    "We've seen how to define a function's input and process. Now, what about its output? So far, the functions we have implemented printed their results, but that is not always appropriate.\n",
    "\n",
    "Often, another part of the program needs to use a result in a calculation. We have already seen an example of this in Section 1, where `sum()` and `len()` outputs were combined to calculate a mean value. When implementing our own functions, we can give a result back to the code which called the function using the `return` keyword.\n",
    "\n",
    "`return` immediately ends the current function call and sends a value back to its **caller**, the piece of code that ran the function. The caller can then do whatever it wants with it: assign it to a variable, use it in an expression, print it, et cetera.\n",
    "\n",
    "**Predict:** What values will the two `print()` calls display?\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c015",
   "metadata": {},
   "outputs": [],
   "source": [
    "def square(number):\n",
    "    return number * number\n",
    "\n",
    "result = square(5)\n",
    "print(result)\n",
    "\n",
    "print(square(3) + square(4))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c016",
   "metadata": {},
   "source": [
    "Here, `square()` only _returned_ a value. It did not print anything by itself; instead, the _caller_ decided what to do with the result. In the first example, the caller assigned the output of `square(5)` to a variable called `result`.\n",
    "\n",
    "In the second example we see an example of the output of two functions becoming the input of another one:\n",
    "1. The outputs of both `square(3)` and `square(4)` were provided as operands to the `+` operator;\n",
    "2. Together, they formed the expression `9 + 16` which was evaluated to be `25`; which,\n",
    "3. Finally, was provided as input to the `print()` statement.\n",
    "\n",
    "Let's look at another example. Both functions below double a number, but one prints it and the other only returns the doubled value.\n",
    "\n",
    "**Predict:** How many lines will appear? What is stored in `printed_result`?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c017",
   "metadata": {},
   "outputs": [],
   "source": [
    "def print_double(number):\n",
    "    print(number * 2)\n",
    "\n",
    "def return_double(number):\n",
    "    return number * 2\n",
    "\n",
    "printed_result = print_double(6)\n",
    "returned_result = return_double(6)\n",
    "print(f\"print_double returned {printed_result}\")\n",
    "print(f\"double returned {returned_result}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c018",
   "metadata": {},
   "source": [
    "As you saw, a function which reaches the end of its body without a `return` statement returns `None`. A `return` with no value after it also returns `None`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "383889cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "def nothing_to_see_here():\n",
    "\treturn\n",
    "\n",
    "print(nothing_to_see_here())\n",
    "print(type(nothing_to_see_here()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c019",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "source": [
    "`None` is a special value that represents the _absence_ of a result. It has its own type, `NoneType`, and counts as `False` when converted with `bool()`, just like `0` or the empty string. \n",
    "\n",
    "This explains the output above: `print_double(6)` displayed `12`, but because the function did not _return_ anything, the value assigned to `printed_result` is `None`.\n",
    "\n",
    "`double(6)`, however, returned `12`, which we can use in further calculations. In this case, if we wanted the value printed, we need to call `print()` separately, as the function did not do that by itself.\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: reduce, return, recycle</b>\n",
    "\n",
    "Define `minutes_to_seconds(minutes)` that returns the conversion. Use the returned values to calculate the total seconds in 3 minutes plus 2 minutes.\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c020",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "# Your code here!\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c021",
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "source": [
    "<details>\n",
    "<summary><b>Click for the solution</b></summary>\n",
    "\n",
    "```python\n",
    "def minutes_to_seconds(minutes):\n",
    "    return minutes * 60\n",
    "\n",
    "total_seconds = minutes_to_seconds(3) + minutes_to_seconds(2)\n",
    "print(total_seconds)\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9be702f9",
   "metadata": {},
   "source": [
    "As said, reaching `return` immediately ends the function call. This works in a loop too, even if it has more items to visit.\n",
    "\n",
    "This can be useful when we're looking for a particular result and can stop the function when we've found it. Be careful with it though, as putting `return` too early could accidentally skip work!\n",
    "\n",
    "**Predict:** What does the following function do? Trace its steps and write down every line printed before running it. After running it, test it out with a different list. What is returned when you give it a list with _no_ even numbers?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "00a52a21",
   "metadata": {},
   "outputs": [],
   "source": [
    "def first_even(numbers):\n",
    "    for number in numbers:\n",
    "        print(\"Checking\", number)\n",
    "        if number % 2 == 0:\n",
    "            return number\n",
    "\n",
    "print(first_even([1, 3, 4, 6]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f95e4b5f",
   "metadata": {},
   "source": [
    "## 4. Positional vs keyword arguments and default values"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6207cb54",
   "metadata": {},
   "source": [
    "By default, the arguments in Python functions are **positional**: that is, you supply them in the order that you write the parameters in the function definition.\n",
    "\n",
    "Alternatively, Python allows you to _explicitly name_ the parameters you are supplying an argument for in its call. These are called **keyword arguments**.\n",
    "\n",
    "**Predict:** Do the three calls below return the same result?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e639164",
   "metadata": {},
   "outputs": [],
   "source": [
    "def rectangle_area(width, height):\n",
    "    return width * height\n",
    "\n",
    "print(rectangle_area(3, 4))\n",
    "print(rectangle_area(width=3, height=4))\n",
    "print(rectangle_area(height=4, width=3))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ae85b25",
   "metadata": {},
   "source": [
    "Sometimes, most calls need the same value for a parameter. A **default value** makes a parameter _optional_ and supplies a back-up value if the caller does not provide one.\n",
    "\n",
    "For example, the following function usually formats amounts in pounds, but the caller can choose a different currency when needed:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d43f7655",
   "metadata": {},
   "outputs": [],
   "source": [
    "def format_price(amount, currency=\"£\"):\n",
    "    return f\"{currency}{amount:.2f}\"\n",
    "\n",
    "print(format_price(3.5))\n",
    "print(format_price(3.5, \"$\"))\n",
    "print(format_price(amount=3.5, currency=\"€\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be8d7813-d14b-4890-bfb2-21f910a3ce21",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-warning\">\n",
    "<b>Watch out: different uses of <code>=</code></b>\n",
    "\n",
    "Using `=` in function parentheses has a different meaning depending on whether you are using it in a function _definition_ or in a function _call_.\n",
    "\n",
    "In `def format_price(amount, currency=\"£\")`, it gives `currency` a _default_. In `format_price(amount=3.5, currency=\"€\")`, it supplies _keyword arguments_ for this one call.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c61af899",
   "metadata": {},
   "source": [
    "Using defaults and keyword arguments can get messy quickly. To help you keep things organised, Python requires you to follow these rules:\n",
    "\n",
    "- In a definition, put required parameters before parameters with defaults.\n",
    "- In a call that mixes the two forms, put positional arguments before keyword arguments.\n",
    "- Supply each parameter only once.\n",
    "\n",
    "These are _hard_ rules: your Python code will not run when you break them. Breaking either of the first two causes a `SyntaxError`, breaking the third one causes a `TypeError`.\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: optional formatting</b>\n",
    "\n",
    "Define a function named `describe_temperature` that has three parameters: `value`, `unit`, and `include_label`. `unit` should default to `\"C\"` and `include_label` to `True`. The function should return a temperature formatted with one decimal place, such as `\"12.5°C\"`. When `include_label` is `True`, add `\"Temperature: \"` at the beginning of the returned string.\n",
    "\n",
    "Call it once with only one value. Then display `54.5°F` without the label. Finally, try supplying the two optional arguments in the opposite order.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7c71333d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Your code here!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "691f142d",
   "metadata": {},
   "source": [
    "<details><summary><b>Click for the solution</b></summary>\n",
    "\n",
    "```python\n",
    "def describe_temperature(value, unit=\"C\", include_label=True):\n",
    "    formatted = f\"{value:.1f}°{unit}\"\n",
    "    if include_label:\n",
    "        return f\"Temperature: {formatted}\"\n",
    "    return formatted\n",
    "\n",
    "print(describe_temperature(12.5))\n",
    "print(describe_temperature(54.5, \"F\", False))\n",
    "print(describe_temperature(54.5, include_label=False, unit=\"F\"))\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e212c971",
   "metadata": {},
   "source": [
    "## 5. Documenting a function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "635bef81",
   "metadata": {},
   "source": [
    "A very brief diversion. You've seen, so far, that strings can be written using either single quotes `'` or double ones `\"`.\n",
    "\n",
    "These strings always have to fit on one line. However, Python allows you to write **multi-line strings** which are denoted by _triple quotation marks_, of either the single or double kind."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c1cc560",
   "metadata": {},
   "outputs": [],
   "source": [
    "single = \"I have to fit on this line\"\n",
    "\n",
    "multiple = '''But I\n",
    "don't\n",
    "have to!'''\n",
    "\n",
    "haiku = \"\"\"\n",
    "Endless threads of code\n",
    "Woven tight through triple quotes\n",
    "In scope, out of touch\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fcdc0c46",
   "metadata": {},
   "source": [
    "This is particularly useful if you want to preserve newlines in your text.\n",
    "\n",
    "That's not all that triple-quoted text is good for, however. They are also used to provide documentation to functions in a neat Python feature called **docstrings**.\n",
    "\n",
    "A docstring is a string that is written on the first line of a function's body. Unlike comments, which are completely ignored, docstrings are picked up by Python and added to the language's documentation system. In the piece of code below, if you place your cursor over any instance of the word  `percentage`, you hold <kbd>&#x21E7; Shift</kbd> and press <kbd>&#x21E5; Tab</kbd> the docstring shows up in a tooltip (*note: in VS Code, hovering over a function has the same effect*).\n",
    "\n",
    "In addition, you can print the docstring by calling the built-in `help()` function. Note that when using `help()`, you should only pass it the name of the function you want the documentation of, and _not_ its parentheses! That would call it, whereas `help()` is only interested in information _about_ the function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "96b5f2e9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def percentage(part, whole):\n",
    "    \"\"\"Return part as a percentage of a non-zero whole.\"\"\"\n",
    "    return part / whole * 100\n",
    "\n",
    "print(percentage(18, 24))\n",
    "help(percentage) # Note we only write the function's name, because we are not calling it!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d872d92",
   "metadata": {},
   "source": [
    "Python's built-in functions have docstrings too, so you can always run `help()` if you're unsure about how to use a function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "09620d2c",
   "metadata": {},
   "outputs": [],
   "source": [
    "help(sum)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34e29a05",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "<b>Note: docstring conventions</b>\n",
    "\n",
    "Docstrings don't _have_ to be written as triple-quoted string; regular strings will be picked up by Python and treated as a docstring as well. However, in the community, it's the convention to use triple-quoted strings (using double rather than single quotes) for docstrings, so that is what we teach here and what all of our examples will use.\n",
    "</div>\n",
    "\n",
    "Docstrings are useful in particular to describe a function's result as well as any assumptions the caller needs to know. For example, in the `percentage` docstring we specified that `whole` must be non-zero, because division by zero will throw an error, and the `sum` docstring specifies that the function should be used with numeric values.\n",
    "\n",
    "Why write docstrings instead of just comments? Because functions are reusable, they might pop up anywhere in your code. Using a docstring means you can always quickly access a function's relevant information, saving you and your collaborators time.\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: initial comments</b>\n",
    "\n",
    "Write a function, `initials`, that takes in a `first_name` and `last_name` to return the two uppercase initials and give it a docstring which states that both names must be non-empty strings.\n",
    "\n",
    "Call it with `\"Ada\"` and `\"Lovelace\"` and then use `help()` to read your documentation.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f294d627",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Your code here!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "104bca78",
   "metadata": {},
   "source": [
    "<details><summary><b>Click for a possible solution</b></summary>\n",
    "\n",
    "```python\n",
    "def initials(first_name, last_name):\n",
    "    \"\"\"Return uppercase initials. Both names must be non-empty strings.\"\"\"\n",
    "    return first_name[0].upper() + last_name[0].upper()\n",
    "\n",
    "print(initials(\"Ada\", \"Lovelace\"))\n",
    "help(initials)\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4e9066a0",
   "metadata": {},
   "source": [
    "## 6. Variable scope"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c022",
   "metadata": {},
   "source": [
    "Like we did last lecture, now that we've seen all new syntax, we'll finish with a more theoretical discussion that our new knowledge has opened up.\n",
    "\n",
    "Each variable has a certain **scope**, which is the part of a program where its name can be used. The variables that we've seen before this lecture were all **global**, which means that they could be used anywhere in the file or notebook in which they were declared.\n",
    "\n",
    "By contrast, parameters as well as variables assigned inside a function are **local** to that function call, which means they can only be used inside that function.\n",
    "\n",
    "In the next example, `message` has global scope, whereas `text` and `local_message` belong to the function call. While we can use them inside the function, those names are not available outside of it. Trying to use a name that is not available causes a `NameError`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c023",
   "metadata": {},
   "outputs": [],
   "source": [
    "message = \"global message\"\n",
    "\n",
    "def demonstrate_scope(text):\n",
    "    print(message) # message has global scope so can be used anywhere\n",
    "    local_message = text.upper() # However, this variable is local to demonstrate_scope\n",
    "    print(local_message)\n",
    "\n",
    "demonstrate_scope(\"inside the function\")\n",
    "print(message)\n",
    "# print(local_message) or print(text) would raise a NameError as they are not accessible outside"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b391dab",
   "metadata": {},
   "source": [
    "This also means that technically, we can use the same names for variables inside and outside of functions and they would refer to completely separate variables:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c024",
   "metadata": {},
   "outputs": [],
   "source": [
    "def average_of_two(first, second):\n",
    "    total = first + second # This name is inside the function...\n",
    "    return total / 2\n",
    "\n",
    "total = 100  # ...and this one is out, and is not influenced by what happens inside of average_of_two\n",
    "print(average_of_two(10, 20))\n",
    "print(total)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c025",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "source": [
    "However, this is **not recommended**. While the code runs fine, it's easy to get confused when you reuse the same name!\n",
    "\n",
    "In fact, while functions can use global variables just fine, it's good practice to pass all values as arguments to make it clear what is used where.\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: pass the information in</b>\n",
    "\n",
    "Rewrite `cost()` so it receives `rate` as a parameter instead of reading `hourly_rate`. Return the cost and call your function with two different rates.\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-c026",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "hourly_rate = 15\n",
    "\n",
    "def cost(hours):\n",
    "    return hours * hourly_rate\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-c027",
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "source": [
    "<details>\n",
    "<summary><b>Click for a possible solution</b></summary>\n",
    "\n",
    "```python\n",
    "def cost(hours, rate):\n",
    "    return hours * rate\n",
    "\n",
    "print(cost(4, 15))\n",
    "print(cost(4, 18))\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98089ac7",
   "metadata": {},
   "source": [
    "### Local parameters and shared objects\n",
    "\n",
    "While parameters and names assigned inside of functions are local to a function call, they can refer to the same object as names outside of the function.\n",
    "\n",
    "Just like we saw in the last lecture, this can potentially cause unexpected behaviour when working with mutable objects, such as lists, dictionaries, and sets.\n",
    "\n",
    "**Predict:** What will `tasks` contain after the call? Does the function need to return the list for the change to be visible?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1756b061",
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_task(task_list, task):\n",
    "    task_list.append(task)\n",
    "\n",
    "global_tasks = [\"read\"]\n",
    "add_task(global_tasks, \"practise\")\n",
    "print(global_tasks)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee34c044",
   "metadata": {},
   "source": [
    "`add_task` does what it says without returning anything because `task_list` refers to a mutable object. While the _name_ `task_list` can only be used inside of `add_task`, when it is supplied `global_tasks` as an argument, both names now point towards the same list. As the list is mutable, `task_list.append(task)` updates it in place meaning both names now point to the updated list.\n",
    "\n",
    "If a function should leave its input unchanged, it can work on a copy and return that copy. You can use a docstring to make clear how the function operates."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2315ff06",
   "metadata": {},
   "outputs": [],
   "source": [
    "def with_task(task_list, task):\n",
    "    \"\"\"Return a copy of `task_list` with `task` added, while leaving `task_list` unchanged.\"\"\"\n",
    "    updated = task_list.copy()\n",
    "    updated.append(task)\n",
    "    return updated\n",
    "\n",
    "tasks = [\"read\"]\n",
    "new_tasks = with_task(tasks, \"practise\")\n",
    "\n",
    "print(tasks)\n",
    "print(new_tasks)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-consolidation-prompt",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "source": [
    "## Consolidation challenge\n",
    "\n",
    "You know the drill, one more challenge to wrap things up.\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: preview a playlist change</b>\n",
    "\n",
    "Let's write a function for adding songs to a shared playlist. It has options for adding the song at the beginning or at the end, as well as both an 'update' mode (that changes the playlist) and a 'preview' mode (that only shows what an updated playlist would look like without actually updating the real list).\n",
    "\n",
    "Define a function named `queue_song` that takes in a list `queue`, string `song`, and booleans `play_next` and `change_original` which both default to `False`. In its default setup, the function should add `song` to the end of a copy of `queue` and return the updated list. When `play_next` is `True`, put the song at the front instead. When `change_original` is `True`, update the original queue rather than a copy.\n",
    "\n",
    "Finally, give the function a docstring explaining what it returns and when it changes the input list.\n",
    "\n",
    "After defining the function, first print your docstring to test it's working. Then, preview adding `\"We Will Rock You\"` to the front of the queue and print both `songs` and the returned preview. Then add `\"Bohemian Rhapsody\"` to the original queue and print `songs` and the returned list again.\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l06-consolidation-work",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "songs = [\"Bicycle Race\", \"Killer Queen\"]\n",
    "\n",
    "# Your function here!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-consolidation-solution",
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "source": [
    "<details><summary><b>Click for the solution</b></summary>\n",
    "\n",
    "```python\n",
    "songs = [\"Bicycle Race\", \"Killer Queen\"]\n",
    "\n",
    "def queue_song(queue, song, play_next=False, change_original=False):\n",
    "    \"\"\"Return a `queue` with `song` added. Change the input only if `change_original` is True.\"\"\"\n",
    "    if change_original:\n",
    "        updated = queue\n",
    "    else:\n",
    "        updated = queue.copy()\n",
    "    \n",
    "    if play_next:\n",
    "        updated.insert(0, song)\n",
    "    else:\n",
    "        updated.append(song)\n",
    "    \n",
    "    return updated\n",
    "\n",
    "help(queue_song)\n",
    "\n",
    "preview = queue_song(songs, \"We Will Rock You\", play_next=True)\n",
    "print(songs)   # ['Bicycle Race', 'Killer Queen']\n",
    "print(preview) # ['We Will Rock You', 'Bicycle Race', 'Killer Queen']\n",
    "\n",
    "committed = queue_song(songs, \"Bohemian Rhapsody\", change_original=True)\n",
    "print(songs)     # ['Bicycle Race', 'Killer Queen', 'Bohemian Rhapsody']\n",
    "print(committed) # ['Bicycle Race', 'Killer Queen', 'Bohemian Rhapsody']\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l06-consolidation-summary",
   "metadata": {
    "tags": [
     "summary"
    ]
   },
   "source": [
    "## Summary\n",
    "\n",
    "We are getting close to the end of Part 1 of the course... well done. One more full lecture and then the two after are lighter on new concepts.\n",
    "\n",
    "### Key terms\n",
    "\n",
    "<dl>\n",
    "\t<dt><b>Function</b></dt>\n",
    "\t<dd>A reusable piece of code which can receive arguments and return a value.</dd>\n",
    "\t<dt><b>Parameter</b></dt>\n",
    "\t<dd>A name in a function definition which receives an argument when the function is called.</dd>\n",
    "\t<dt><b>Argument</b></dt>\n",
    "\t<dd>A value supplied to a function call as input.</dd>\n",
    "\t<dt><b>Caller</b></dt>\n",
    "\t<dd>The piece of code that calls a function and receives its returned value.</dd>\n",
    "\t<dt><b>Return value</b></dt>\n",
    "\t<dd>The value a function sends back to the caller. A function without an explicit return value returns <code>None</code>.</dd>\n",
    "\t<dt><b>Positional argument</b></dt>\n",
    "\t<dd>An argument matched to a parameter by its position in the call.</dd>\n",
    "\t<dt><b>Keyword argument</b></dt>\n",
    "\t<dd>An argument matched to a parameter by its name.</dd>\n",
    "\t<dt><b>Default value</b></dt>\n",
    "\t<dd>A value used for a parameter when the caller omits that argument.</dd>\n",
    "\t<dt><b>Docstring</b></dt>\n",
    "\t<dd>A string at the beginning of a function body which documents its purpose and use. Conventionally written using triple quotes.</dd>\n",
    "\t<dt><b>Scope</b></dt>\n",
    "\t<dd>The part of a program in which a name can be used.</dd>\n",
    "\t<dt><b>Globally scoped variables</b></dt>\n",
    "\t<dd>Variables that can be used anywhere in a program or notebook.</dd>\n",
    "\t<dt><b>Locally scoped variables</b></dt>\n",
    "\t<dd>Variables that can only be used inside a block. For example, parameters and variables declared inside of a function are locally scoped to that function.</dd>\n",
    "</dl>\n",
    "\n",
    "### New syntax\n",
    "\n",
    "<dl>\n",
    "\t<dt><code>min(values)</code>, <code>max(values)</code>, and <code>sum(values)</code></dt>\n",
    "\t<dd>Returns the smallest value in, largest value in, and total of <code>values</code>, respectively.</dd>\n",
    "\t<dt><code>def name(parameter):</code></dt>\n",
    "\t<dd>Defines a function with an indented body. The body runs when the function is called and given access to any parameters.</dd>\n",
    "\t<dt><code>return value</code></dt>\n",
    "\t<dd>Ends the current function call and gives <code>value</code> back to its caller.</dd>\n",
    "\t<dt><code>None</code></dt>\n",
    "\t<dd>A special value representing the absence of a result. Returned when a function does not otherwise returns a value.</dd>\n",
    "\t<dt><code>NoneType</code></dt>\n",
    "\t<dd>A special type for the <code>None</code> value.</dd>\n",
    "\t<dt><code>def name(parameter=default):</code></dt>\n",
    "\t<dd>Gives a parameter a default value, making it optional. Make sure to write required parameters before parameters with defaults.</dd>\n",
    "\t<dt><code>name(parameter=value)</code></dt>\n",
    "\t<dd>Supplies an argument by keyword when calling a function.</dd>\n",
    "\t<dt><code>'''Text.'''</code> and <code>\"\"\"Text.\"\"\"</code></dt>\n",
    "\t<dd>Denotes a multi-line string. By convention, the double-quoted variant is also used to write a docstring as the first statement in a function body.</dd>\n",
    "\t<dt><code>help(function)</code></dt>\n",
    "\t<dd>Displays information about a function, including its parameters and docstring.</dd>\n",
    "\t<dt><code>NameError</code></dt>\n",
    "\t<dd>A type of error caused by using a name that is not available in the current scope.</dd>\n",
    "</dl>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7134967b-d96f-46e0-ba7a-583f2882a948",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "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.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
