{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Modules and imports\n",
    "\n",
    "Every `import` statement you write is doing more than meets the eye —\n",
    "finding code, loading it, caching it, and binding names into your\n",
    "current namespace. This tutorial unpacks what `import` actually does,\n",
    "so the rest of this guide rests on a solid foundation.\n",
    "\n",
    "**Time commitment:** 15 minutes\n",
    "\n",
    "**Prerequisites:**\n",
    "\n",
    "- Comfortable with functions and basic data structures\n",
    "- You've used `import math` or similar at some point\n",
    "\n",
    "## Learning objectives\n",
    "\n",
    "By the end of this tutorial, you will be able to:\n",
    "\n",
    "- Explain what a module is and how Python finds one\n",
    "- Use the four common forms of the `import` statement deliberately\n",
    "- Inspect `sys.modules` and `sys.path` to debug an import\n",
    "- Explain the `if __name__ == \"__main__\":` idiom"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What is a module?\n",
    "\n",
    "A module is, simply, a `.py` file. The filename — minus the\n",
    "extension — is the module's name. When you write `import math`,\n",
    "Python is looking for a file called `math.py` (or, in this case,\n",
    "a built-in equivalent that ships with the interpreter).\n",
    "\n",
    "Run the cell below to import the standard library's `math` module\n",
    "and use it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "print(math.pi)\n",
    "print(math.sqrt(2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that you have to write `math.pi`, not just `pi`. The `import`\n",
    "statement doesn't dump the module's contents into your namespace — it\n",
    "creates a single name (`math`) that *refers* to the module, and you\n",
    "reach into it with the dot."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Four forms of `import`\n",
    "\n",
    "Almost everything you'll see in real code uses one of four forms."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1. Import the whole module under its own name.\n",
    "import math\n",
    "math.pi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 2. Import specific names from the module into your namespace.\n",
    "from math import pi, sqrt\n",
    "pi, sqrt(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 3. Rename on import — useful when names clash, or for conventional\n",
    "#    short aliases like `import numpy as np`.\n",
    "import math as m\n",
    "m.pi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 4. Import a name and rename it.\n",
    "from math import sqrt as square_root\n",
    "square_root(9)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A fifth form — `from math import *` — also exists, but it's almost\n",
    "always a mistake outside of an interactive session. It pulls every\n",
    "public name from the module into your namespace, where it can shadow\n",
    "your own variables silently."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What `import` actually does\n",
    "\n",
    "When Python encounters `import math` for the first time in a session,\n",
    "roughly this happens:\n",
    "\n",
    "1. Check `sys.modules`, an in-memory cache of already-imported modules.\n",
    "   If `math` is there, bind it to the local name and stop.\n",
    "2. Otherwise, walk through `sys.path` — a list of directories — looking\n",
    "   for a matching file or package.\n",
    "3. Execute the module's code top-to-bottom, building up its namespace.\n",
    "4. Store the result in `sys.modules` so the next `import math` is a\n",
    "   cheap dictionary lookup.\n",
    "\n",
    "That cache is why running `import math` ten times in a script costs\n",
    "almost nothing after the first."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "\n",
    "# math is now in the cache.\n",
    "print(\"math in cache?\", \"math\" in sys.modules)\n",
    "print(\"modules cached:\", len(sys.modules))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The first few entries of sys.path — the search list.\n",
    "for entry in sys.path[:5]:\n",
    "    print(repr(entry))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The first entry is typically the directory of the script you ran (or\n",
    "an empty string in an interactive session, meaning \"current\n",
    "directory\"). The rest are the standard library and any installed\n",
    "packages. When an `import` mysteriously fails with `ModuleNotFoundError`,\n",
    "the question to ask is almost always: *\"is the module's parent\n",
    "directory on `sys.path`?\"*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `if __name__ == \"__main__\":`\n",
    "\n",
    "Every module has a `__name__` attribute. When a module is imported,\n",
    "`__name__` is set to the module's name (e.g. `\"math\"`). When a module\n",
    "is *run directly* — `python myscript.py` — `__name__` is set to the\n",
    "special string `\"__main__\"`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# In a notebook, the cell's __name__ is \"__main__\" — the notebook\n",
    "# itself is the top-level script.\n",
    "print(__name__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That's the basis for the idiom you'll see at the bottom of well-shaped\n",
    "scripts:\n",
    "\n",
    "```python\n",
    "def main():\n",
    "    ...\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()\n",
    "```\n",
    "\n",
    "It means *\"only run `main()` when this file is executed directly, not\n",
    "when it's imported by something else\"*. Without it, simply importing\n",
    "the module would run its main routine — which is rarely what an\n",
    "importing caller wants."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Reloading during development\n",
    "\n",
    "Imports are cached, so re-running `import math` after you've edited\n",
    "`math.py` won't pick up your changes. The standard library's\n",
    "`importlib.reload()` exists for this case, but in practice you'll\n",
    "almost always restart the Python process instead — much simpler, and\n",
    "avoids the surprises that come with half-reloaded state.\n",
    "\n",
    "Notebooks add their own wrinkle: an editor's autoreload extension can\n",
    "re-import on save, which is convenient until it isn't. Keep\n",
    "`importlib.reload()` in your back pocket; reach for it sparingly."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Recap and next steps\n",
    "\n",
    "- A module is a `.py` file; its name is the filename without the extension.\n",
    "- `import x` binds `x` to the module; `from x import y` binds the inner\n",
    "  name into your namespace.\n",
    "- Imports are cached in `sys.modules`; module files are searched along\n",
    "  `sys.path`.\n",
    "- `__name__ == \"__main__\"` distinguishes \"I'm being run directly\"\n",
    "  from \"I've been imported\".\n",
    "\n",
    "Next up: when one `.py` file isn't enough — [Packages and namespaces](https://agilearn.co.uk/guides/packages-and-packaging/learn/02-packages-and-namespaces)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}