{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "l11-c000",
   "metadata": {},
   "source": [
    "# Lecture 11 - Modules\n",
    "\n",
    "**Authors:** Fiona McNeill, Matteo Degiacomi, Rayo Verweij, and Nigel Topham\n",
    "\n",
    "## Introduction\n",
    "\n",
    "Python allows us share functionality between projects by grouping code in modules. In this notebook, we'll discuss how to import them, find out how they work, and organise some of our own functions into a module.\n",
    "\n",
    "### Learning outcomes\n",
    "\n",
    "In this notebook, we'll discuss:\n",
    "\n",
    "- importing modules and choosing an import style;\n",
    "- using `dir()` and `help()` to explore unfamiliar tools;\n",
    "- constructing and inspecting file paths with `pathlib`;\n",
    "- grouping related functions into modules; and\n",
    "- using `main()` and `__name__` to separate reusable definitions from a demonstration.\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",
    "<details>\n",
    "<summary>Click to expand</summary>\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",
    "</details>\n",
    "\n",
    "### Navigating the notebook\n",
    "\n",
    "A Jupyter notebook can be operated with your keyboard. Here is a **cheat sheet**:\n",
    "\n",
    "<details>\n",
    "<summary>Click to expand</summary>\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",
    "</details>\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: [Modules](https://docs.python.org/3/tutorial/modules.html) - sections 6.1 through 6.3. See also [`pathlib` basic examples](https://docs.python.org/3/library/pathlib.html#basic-use).\n",
    "- W3Schools: [Modules](https://www.w3schools.com/python/python_modules.asp).\n",
    "- Helsinki Python MOOC: [Modules](https://programming-26.mooc.fi/part-7/1-modules), [Randomness](https://programming-26.mooc.fi/part-7/2-randomness), and [Creating your own modules](https://programming-26.mooc.fi/part-7/5-creating-modules).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fdf26a25",
   "metadata": {},
   "source": [
    "## 1. Importing a module"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c001",
   "metadata": {},
   "source": [
    "A **module** is a unit of Python code with its own name. Many modules are Python files containing related definitions. The **standard library** is the collection of modules that is supplied with Python by default, which we can use without installing anything else.\n",
    "\n",
    "For example, Python includes a `math` module with loads of functions related to, you guessed it, mathematics. We can access a module in our code using an `import` statement. By convention, `import` statements are always written at the very top of the file.\n",
    "\n",
    "**Predict:** Which name below refers to a function, and which to a number? What will be printed?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c002",
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "print(math.pi)\n",
    "print(math.sqrt(81))\n",
    "print(math.log(8, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c003",
   "metadata": {},
   "source": [
    "As you can see,\n",
    "\n",
    "- `math.pi` contains an approximation of the number π;\n",
    "- `math.sqrt(n)` calculates the square root of a number `n`; and\n",
    "- `math.log(a, b)` calculates the logarithm of `a` to base `b`.\n",
    "\n",
    "Each module has its own **namespace**, a collection of names and objects they refer to. `pi`, `sqrt()`, and `log()` are all names on the `math` namespace. While accessing names from a namespace uses dot notation like accessing attributes and methods on a class, they are completely different things. To distinguish them, namespaces are written in lowercase while class names are capitalised.\n",
    "\n",
    "You do not have to import everything from a namespace - you can also import names directly `from` a namespace, as follows:\n",
    "\n",
    "**Predict:** Before running the next program, can you guess what it does from the imported names?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c004",
   "metadata": {},
   "outputs": [],
   "source": [
    "from random import choice, randint\n",
    "\n",
    "random_nums = []\n",
    "for i in range(1, 10):\n",
    "\trandom_nums.append(randint(1, 10))\n",
    "\n",
    "print(random_nums)\n",
    "print(choice(random_nums))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ab5e80d",
   "metadata": {},
   "source": [
    "`random` is another module in the Python standard library. Because we directly imported the functions from the module, we did not have to specify the namespace in the code itself.\n",
    "\n",
    "- `random.choice()` returns a randomly picked item from a collection; and\n",
    "- `random.randit()` returns a randomly picked integer between the given numbers **inclusive**\n",
    "\n",
    "**Note:** What is a key difference between the parameters of `range()` and `random.randint()`?\n",
    "\n",
    "<details>\n",
    "<summary><b>Click for the answer</b></summary>\n",
    "\n",
    "`range()` creates a range from the first number to the second *exclusive*, i.e. `range(1, 10)` generates the numbers 1 through 9. By contrast, calling `random.randint(1, 10)` can pick any number from 1 up to *and including* 10.\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a7e6597",
   "metadata": {},
   "source": [
    "If we are dealing with a particularly long namespace, we can give our imports an **alias**, using the `as` keyword:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "49f2661c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import statistics as stats\n",
    "\n",
    "values = [4, 7, 9, 10]\n",
    "print(stats.mean(values))\n",
    "print(stats.median(values))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bfbfbb4",
   "metadata": {},
   "source": [
    "Aliases simply change what we call a namespace in our own code, nothing else.\n",
    "\n",
    "- `statistics.mean()` calculates the average of a list of numbers; and\n",
    "- `statistics.median()` finds the middle value after sorting from low to high, or the average of the two middle values."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c006",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: choosing import forms</b>\n",
    "\n",
    "Import `ceil` directly from `math`, and import `statistics` as `stats`. `ceil(number)` rounds `number` upwards to the nearest integer.\n",
    "\n",
    "Use `ceil()` to round up every number in the list provided and then calculate the average of the new list of numbers.\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c007",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "values = [2.2, 3.8, 4.1]\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c008",
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "source": [
    "<details><summary><b>Click for a possible solution</b></summary>\n",
    "\n",
    "```python\n",
    "from math import ceil\n",
    "import statistics as stats\n",
    "\n",
    "values = [2.2, 3.8, 4.1]\n",
    "ceil_values = []\n",
    "\n",
    "for val in values:\n",
    "    ceil_values.append(ceil(val))\n",
    "\n",
    "print(stats.mean(ceil_values))\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb790c94",
   "metadata": {},
   "source": [
    "## 2. Inspecting a module"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c009",
   "metadata": {},
   "source": [
    "`dir(module)` returns a list of names available in a module.\n",
    "\n",
    "`help(function)`, as we have seen in Lecture 6, displays the function's documentation, including its parameters and often a short example.\n",
    "\n",
    "Some names in `dir()` begin with underscores, like `__doc__` or `__package__`. These are often internal details that you can ignore.\n",
    "\n",
    "**Predict:** From their names, what do you think `math.factorial()` and `math.floor()` do?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c010",
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "print(dir(math))\n",
    "\n",
    "help(math.factorial)\n",
    "help(math.floor)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c011",
   "metadata": {},
   "source": [
    "While long lists of names can be hard to browse, we can use loops to find names containing words we're interested in.\n",
    "\n",
    "The next cell looks for names containing `mean`. It then displays help for `statistics.mean`, which we've already tried."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c012",
   "metadata": {},
   "outputs": [],
   "source": [
    "import statistics\n",
    "\n",
    "for name in dir(statistics):\n",
    "    if \"stdev\" in name:\n",
    "        print(name)\n",
    "\n",
    "help(statistics.stdev)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c013",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: investigate a module</b>\n",
    "\n",
    "Find names in `random` containing `sample` or `choice` and read `help(random.sample)`.\n",
    "\n",
    "Use the function to select two random items from the list of letters provided. What does the parameter `k` mean?\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c014",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "import random\n",
    "\n",
    "letters = [\"A\", \"B\", \"C\", \"D\", \"E\"]\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c015",
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "source": [
    "<details><summary><b>Click for a possible solution</b></summary>\n",
    "\n",
    "`k` is the number of items to select. `sample()` selects without replacement: each position in the list is chosen at most once. If the list contains no repeated values, no value appears twice in the result.\n",
    "\n",
    "```python\n",
    "import random\n",
    "\n",
    "letters = [\"A\", \"B\", \"C\", \"D\", \"E\"]\n",
    "\n",
    "for name in dir(random):\n",
    "    if \"sample\" in name or \"choice\" in name:\n",
    "        print(name)\n",
    "\n",
    "help(random.sample)\n",
    "\n",
    "print(random.sample(letters, k=2))\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9d17a95",
   "metadata": {},
   "source": [
    "## 3. Working with paths"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2502b799",
   "metadata": {},
   "source": [
    "In the previous lecture, we introduced **paths** as addresses for files, and the different ways in which paths are written on Windows and Unix systems. Thankfully, Python has a built-in module, `pathlib`, that greatly simplifies working with paths for us.\n",
    "\n",
    "`pathlib` provides a `Path` class that allows us to construct paths and contains common methods for using them.\n",
    "\n",
    "**Predict:** Based on the following code, what do you think each method does?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2bd9a878",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "relative_path = Path(\"ip26_l11_output\") / \"notes.txt\"\n",
    "\n",
    "print(\"Relative path:\", relative_path)\n",
    "print(\"Working directory:\", Path.cwd())\n",
    "print(\"Absolute?\", relative_path.is_absolute())\n",
    "print(\"Resolved path:\", relative_path.resolve())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3749f377",
   "metadata": {},
   "source": [
    "The `Path()` constructor takes in a string which will be the directory that you'll use as your starting point. You can then create relative paths to different files and subfolders using the `/` operator. `/` will _safely_ create paths: that is, it will automatically use the right syntax whether you're on Windows, macOS, or Linux.\n",
    "\n",
    "Remember that operators can have different functions when working with different types. Just as the `+` operator has different functionality when used with number types (addition) versus strings (concatenation), the `/` operator has different functionality depending on whether you use it with number types (division) compared to `Path` types (path construction).\n",
    "\n",
    "Useful methods include:\n",
    "\n",
    "- `Path.cwd()` which prints the current working directory;\n",
    "- `.is_absolute()` which checks whether a path is absolute or not; and\n",
    "- `.resolve()` which produces an absolute path from a relative one.\n",
    "\n",
    "Note that `.cwd()` is a _class method_, that needs to be called on the `Path` class, whereas the others are _instance methods_, which need to be called on the individual instances of the class.\n",
    "\n",
    "A path also has instance attributes for its final component (`.name`), extension (`.suffix`), and containing directory (`.parent`).\n",
    "\n",
    "**Note:** creating a `Path` object does not necessarily create the *file* at that path. To check whether there's actually a file at a path object, you can use the `.exists()` method.\n",
    "\n",
    "To create a file, you can pass a `Path` object directly to `open()`, which we discussed in the last lecture. To create a folder, you can use the `.mkdir()` method - short for *make directory*. By default, the method will create a new folder if it does not exist yet and throw an error if it does. Optionally you can give it the keyword argument `exist_ok=True`, which suppressed the error. In either case, `.mkdir()` does not create a duplicate of a folder if it doesn't exist yet.\n",
    "\n",
    "**Predict:** What will each of the final three lines print?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54e8ce6c",
   "metadata": {},
   "outputs": [],
   "source": [
    "output_dir = Path(\"ip26_l11_output\")\n",
    "output_dir.mkdir(exist_ok=True)\n",
    "message_path = output_dir / \"message.txt\"\n",
    "\n",
    "with open(message_path, \"w\", encoding=\"utf-8\") as file:\n",
    "    file.write(\"Hello from a path object!\\n\")\n",
    "\n",
    "print(message_path.name)\n",
    "print(message_path.suffix)\n",
    "print(message_path.parent)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1b7affd",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "source": [
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: construct a path</b>\n",
    "\n",
    "Construct a path to `reports/week1.txt` inside `output_dir`. Print its name, parent, and absolute form. You do not need to create the file or the `reports` directory.\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "daea85d4",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "# Your solution here!\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7382a831",
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "source": [
    "<details><summary><b>Click for a possible solution</b></summary>\n",
    "\n",
    "```python\n",
    "report_path = output_dir / \"reports\" / \"week1.txt\"\n",
    "print(report_path.name)\n",
    "print(report_path.parent)\n",
    "print(report_path.resolve())\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4faf30e9",
   "metadata": {},
   "source": [
    "## 4. Building our own modules"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c016",
   "metadata": {},
   "source": [
    "Now that we've gotten a feel for some useful modules in the standard library, let's take the next big step: buiding our own one.\n",
    "\n",
    "Modules aren't just useful for sharing code with others: they can also be a great help in managing your own projects and keeping them organised.\n",
    "\n",
    "Let's look at a small example. Say we're working on a project that allows people to work with distance measurements, including functions to convert them between units and format them nicely. To build our functions, we've already applied the best practices from Lecture 9, including _decomposition_ to break the different tasks into their own units and _DRY_ to not repeat any rules across our program:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c017",
   "metadata": {},
   "outputs": [],
   "source": [
    "def convert_distances(distances_km):\n",
    "    distances_miles = []\n",
    "    for distance in distances_km:\n",
    "        distances_miles.append(distance * 0.621371)\n",
    "    return distances_miles\n",
    "\n",
    "def format_distances(distances_miles):\n",
    "    labels = []\n",
    "    for distance in distances_miles:\n",
    "        labels.append(f\"{distance:.2f} miles\")\n",
    "    return labels\n",
    "\n",
    "distances_km = [1, 5, 10]\n",
    "converted = convert_distances(distances_km)\n",
    "print(format_distances(converted))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c018",
   "metadata": {},
   "source": [
    "Arguably, the calculation function should work whether its input came from a notebook, a file, or a person typing into a program. Modularising our code allows us to optimise for reusability and assigning each part of our code a clear responsibility, a practice called **separation of concerns**. For example, the conversion could live in a file `journey_tools.py`, with formatting in `report_tools.py`, and a third module could collect input, call the functions, and display the result.\n",
    "\n",
    "Module names normally use lowercase letters and underscores. Avoid naming your own modules `math.py`, `random.py`, or the name of another module you use, as Python may use one of your files when you meant to import a standard library module!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a777823",
   "metadata": {},
   "source": [
    "### `main()` and `__name__`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c022",
   "metadata": {},
   "source": [
    "Conveniently, when writing files, Python allows us to give them _different behaviour_ when they are imported versus run directly. This allows us to import useful functions from one module into another, without running the other code in the file that might not be relevant when importing.\n",
    "\n",
    "To do this, we first refactor any \"loose\" code in a file (i.e., code not part of a function or class) into its own function. By convention, we call this function `main()`. For example, the following piece of code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f768518b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def total_cost(prices):\n",
    "    return sum(prices)\n",
    "\n",
    "basket = [2.4, 3.5, 2.8]\n",
    "total = total_cost(basket)\n",
    "print(f\"Total: £{total:.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a613680",
   "metadata": {},
   "source": [
    "Can just as well be written as:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c023",
   "metadata": {},
   "outputs": [],
   "source": [
    "def total_cost(prices):\n",
    "    return sum(prices)\n",
    "\n",
    "def main():\n",
    "    basket = [2.4, 3.5, 2.8]\n",
    "    total = total_cost(basket)\n",
    "    print(f\"Total: £{total:.2f}\")\n",
    "\n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c024",
   "metadata": {},
   "source": [
    "The `main()` function gives the program's overall sequence a name. Here, `total_cost()` does the calculation and `main()` chooses the data and displays the result. We call `main()` at the end to start that sequence.\n",
    "\n",
    "By default, Python supplies each file with a variable called `__name__`. This isn't visible in your code, but it's there all the same:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c025",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(__name__)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "85023ea4",
   "metadata": {},
   "source": [
    "Note the double underscores, like with the magic methods we've discussed in classes!\n",
    "\n",
    "When a Python file runs directly, the value of `__name__` is `\"__main__\"`, and when the file is imported, its value is the module's name. We can use this distinction with a simple `if` statement to check whether a program is running directly or not. This is called a **main guard**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c88b8aa8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def total_cost(prices):\n",
    "    return sum(prices)\n",
    "\n",
    "def main():\n",
    "    basket = [2.4, 3.5, 2.8]\n",
    "    total = total_cost(basket)\n",
    "    print(f\"Total: £{total:.2f}\")\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c026",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "source": [
    "In the above piece of code, running the file directly (including running it in a Python notebook) would define both functions and then call `main()`. However, if you were to import the module, it would simply make the `total_cost()` and `main()` functions available for use, rather than running any code directly.\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "<b>Try it yourself: guard the demonstration</b>\n",
    "\n",
    "Write `rectangle_area(width, height)`. Then write a `main()` that calls it with `4` and `7` and prints the result. Call `main()` inside a main guard.\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c027",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "# Your solution here!\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c028",
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "source": [
    "<details><summary><b>Click for a possible solution</b></summary>\n",
    "\n",
    "```python\n",
    "def rectangle_area(width, height):\n",
    "    return width * height\n",
    "\n",
    "def main():\n",
    "    print(rectangle_area(4, 7))\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2213a4cf",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "<b>Note: <code>__main__</code> in class names</b>\n",
    "\n",
    "When working in Lecture 7, on classes, you might remember that using `type()` on an instance of a class we defined printed something like this:\n",
    "\n",
    "```\n",
    "<class '__main__.ClassName'>\n",
    "```\n",
    "\n",
    "Now, we can understand why this happens. When printing the type of classes outside of the built-in Python ones (such as `str` and `int`), Python automatically adds the namespace of the module the class is from to the type. As in Lecture 7 we defined all of our classes in the same file we were using them in, the namespace for the module was `__main__`. If we had imported the class from another file, the module's name would have been listed in the place of `__main__`.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c5084fa",
   "metadata": {},
   "source": [
    "### Building modules"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-c029",
   "metadata": {},
   "source": [
    "Let's try importing one of our own modules. We can create its file from this notebook using the file-writing code from Lecture 10; we have written a complete Python program inside of a multi-line string with the name `module_source` which we then write to a file. If you execute the below cell, you should see the new file appear in your file browser."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c030",
   "metadata": {},
   "outputs": [],
   "source": [
    "module_source = \"\"\"def kilometres_to_miles(distance):\n",
    "    return distance * 0.621371\n",
    "\n",
    "def main():\n",
    "    print(\"Running the journey demonstration\")\n",
    "    print(kilometres_to_miles(5))\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()\n",
    "\"\"\"\n",
    "\n",
    "with open(\"ip26_l11_journey_tools.py\", \"w\", encoding=\"utf-8\") as file:\n",
    "    file.write(module_source)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea023481",
   "metadata": {},
   "source": [
    "We can import the module simply by using the file's name!\n",
    "\n",
    "**Predict:** Which parts of the imported file will and will not run? What will the imported module's `__name__` be?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-c031",
   "metadata": {},
   "outputs": [],
   "source": [
    "import ip26_l11_journey_tools\n",
    "from ip26_l11_journey_tools import kilometres_to_miles\n",
    "\n",
    "print(ip26_l11_journey_tools.__name__)\n",
    "print(kilometres_to_miles(10))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bee9589e",
   "metadata": {},
   "source": [
    "As the guard is false inside the imported module, its demonstration does not run, but the notebook can still call `kilometres_to_miles()`.\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "<b>Note: the <code>__pycache__</code> folder</b>\n",
    "\n",
    "After this import, a new folder called `__pycache__` appears next to `ip26_l11_journey_tools.py`. Inside it is a file such as `ip26_l11_journey_tools.cpython-311.pyc`: a translated version of your module that Python can load more quickly. The next time the module is imported, Python reuses this file if the `.py` file has not changed since, and quietly rebuilds it if it has.\n",
    "\n",
    "You never need to open or edit these files, and it's safe to delete the folder: Python creates it again on the next import. It is just an optimisation for speed. Your notebook itself does not get one, only the modules it imports.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-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: build a temperature module</b>\n",
    "\n",
    "Create `ip26_l11_temperature_tools.py` from a notebook cell. Give it two functions, `celsius_to_fahrenheit(celsius)` and `mean_temperature(readings)`, plus a `main()` that demonstrates them. You can assume `readings` is non-empty.\n",
    "\n",
    "Protect the demonstration with a main guard. Import your module and call its conversion function with `0`: the result should be `32.0`, without the demonstration running. Finally, call the module's `main()` yourself.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "l11-consolidation-work",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "# Your solution here!\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-consolidation-solution",
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "source": [
    "<details><summary><b>Click for a possible solution</b></summary>\n",
    "\n",
    "If you already imported your own version of `ip26_l11_temperature_tools` in this session, restart the kernel before running this solution. Python loads a module only on its first import, so without a restart the cell below would test your old functions.\n",
    "\n",
    "```python\n",
    "module_source = \"\"\"def celsius_to_fahrenheit(celsius):\n",
    "    return celsius * 9 / 5 + 32\n",
    "\n",
    "def mean_temperature(readings):\n",
    "    return sum(readings) / len(readings)\n",
    "\n",
    "def main():\n",
    "    print(\"Running the temperature demonstration\")\n",
    "    readings = [10, 12, 14]\n",
    "    print(celsius_to_fahrenheit(mean_temperature(readings)))\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()\n",
    "\"\"\"\n",
    "\n",
    "with open(\"ip26_l11_temperature_tools.py\", \"w\", encoding=\"utf-8\") as file:\n",
    "    file.write(module_source)\n",
    "\n",
    "import ip26_l11_temperature_tools\n",
    "\n",
    "assert ip26_l11_temperature_tools.celsius_to_fahrenheit(0) == 32.0\n",
    "ip26_l11_temperature_tools.main()\n",
    "```\n",
    "</details>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "l11-consolidation-summary",
   "metadata": {
    "tags": [
     "summary"
    ]
   },
   "source": [
    "## Summary\n",
    "\n",
    "Well done! Now that you can use modules written by others and make your own ones, a whole new world of possibilities are opened up.\n",
    "\n",
    "Note that you don't need to memorise all the standard library functions that we discussed in this lecture. Knowing where to find them and how to read their documentation is much more useful.\n",
    "\n",
    "### Key terms\n",
    "\n",
    "<dl>\n",
    "\t<dt><b>Module</b></dt>\n",
    "\t<dd>A named unit of Python code containing definitions and statements.</dd>\n",
    "\t<dt><b>Standard library</b></dt>\n",
    "\t<dd>The modules supplied with Python.</dd>\n",
    "\t<dt><b>Namespace</b></dt>\n",
    "\t<dd>A collection of names and the objects they refer to.</dd>\n",
    "\t<dt><b>Alias</b></dt>\n",
    "\t<dd>An alternative name for an object; for example given to imported modules with long names.</dd>\n",
    "\t<dt><b>Separation of concerns</b></dt>\n",
    "\t<dd>Giving different responsibilities to different functions or modules in your code.</dd>\n",
    "\t<dt><b>Main guard</b></dt>\n",
    "\t<dd>A condition that runs a block when a file is executed directly, while skipping it when the file is imported.</dd>\n",
    "</dl>\n",
    "\n",
    "### New syntax\n",
    "\n",
    "<dl>\n",
    "\t<dt><code>import module</code></dt>\n",
    "\t<dd>Makes a module available through its name.</dd>\n",
    "\t<dt><code>from module import name</code></dt>\n",
    "\t<dd>Makes a selected name from a module available directly.</dd>\n",
    "\t<dt><code>import module as alias</code></dt>\n",
    "\t<dd>Imports a module under an alternative name.</dd>\n",
    "\t<dt><code>dir(module)</code></dt>\n",
    "\t<dd>Returns a list of names available in a module.</dd>\n",
    "\t<dt><code>Path(\"directory\") / \"file.txt\"</code></dt>\n",
    "\t<dd>Constructs a path by joining components, after importing <code>Path</code> from <code>pathlib</code>.</dd>\n",
    "\t<dt><code>Path.cwd(), path.resolve(), path.is_absolute()</code></dt>\n",
    "\t<dd>Finds the working directory, forms an absolute path, and checks whether a path is absolute.</dd>\n",
    "\t<dt><code>path.name, path.suffix, path.parent</code></dt>\n",
    "\t<dd>Inspects the last component, extension, and containing directory of a path.</dd>\n",
    "\t<dt><code>path.mkdir(exist_ok=True)</code></dt>\n",
    "\t<dd>Creates a directory, without throwing an error if it already exists.</dd>\n",
    "\t<dt><code>__name__</code></dt>\n",
    "\t<dd>The module's name, or <code>\"__main__\"</code> when Python executes a file directly.</dd>\n",
    "\t<dt><code>if __name__ == \"__main__\":</code></dt>\n",
    "\t<dd>Begins a main guard. Its indented body conventionally calls a function named <code>main()</code>.</dd>\n",
    "</dl>\n"
   ]
  }
 ],
 "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
}
