{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "9a098d66",
   "metadata": {},
   "source": [
    "# Coding Patterns and Idioms\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "contents",
   "metadata": {},
   "source": [
    "```{contents}\n",
    ":local:\n",
    ":depth: 2\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "intro",
   "metadata": {},
   "source": [
    "This notebook is a **recipe reference** \u2014 practical Python idioms used throughout the book. Return here when you encounter a pattern you want to understand or reuse.\n\n**Topics**\n- List comprehensions\n- Lambda functions\n- Docstrings and `help()`\n- Type hints\n- `enumerate()` and `zip()`\n- `try` / `except` for error handling\n- f-string formatting\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c615c48",
   "metadata": {},
   "source": [
    "## List Comprehension and Lambda\n",
    "\n",
    "These are concise tools for transformation logic in scripts.\n",
    "- **Comprehension**: readable filtering/transformation in one expression\n",
    "- **Lambda**: lightweight anonymous function defined inline, without `def`\n",
    "\n",
    "### List Comprehension\n",
    "\n",
    "A list comprehension produces a new list by applying an expression to each item in an iterable, with an optional filter condition:\n",
    "\n",
    "```\n",
    "[expression for item in iterable if condition]\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f223048",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-19T03:58:55.471296Z",
     "iopub.status.busy": "2026-03-19T03:58:55.471227Z",
     "iopub.status.idle": "2026-03-19T03:58:55.473322Z",
     "shell.execute_reply": "2026-03-19T03:58:55.472999Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['alice', 'amy']\n",
      "['bob', 'amy', 'alice', 'charlie']\n"
     ]
    }
   ],
   "source": [
    "names = ['alice', 'bob', 'charlie', 'amy']\n",
    "\n",
    "# filter: keep names starting with 'a'\n",
    "starts_with_a = [n for n in names if n.startswith('a')]\n",
    "\n",
    "# transform: capitalize each name\n",
    "capitalized = [n.capitalize() for n in names]\n",
    "\n",
    "# filter + transform combined\n",
    "short_upper = [n.upper() for n in names if len(n) <= 3]\n",
    "\n",
    "print(starts_with_a)\n",
    "print(capitalized)\n",
    "print(short_upper)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5bce14eb",
   "metadata": {},
   "source": [
    "### Lambda\n",
    "\n",
    "A **lambda** is an anonymous function defined in a single expression. The syntax is:\n",
    "\n",
    "```\n",
    "lambda parameters: expression\n",
    "```\n",
    "\n",
    "- It takes any number of parameters but only **one expression** \u2014 no statements, no `return`.\n",
    "- The expression is implicitly returned.\n",
    "- Lambdas are best used inline, as a `key=` argument or passed to `map()`/`filter()`. For anything more complex, use `def` instead.\n",
    "\n",
    "| Use case | Example |\n",
    "|---|---|\n",
    "| Single argument | `lambda x: x * 2` |\n",
    "| Multiple arguments | `lambda x, y: x + y` |\n",
    "| As a `sorted()` key | `sorted(data, key=lambda s: len(s))` |\n",
    "| With `map()` | `list(map(lambda x: x ** 2, nums))` |\n",
    "| With `filter()` | `list(filter(lambda x: x > 0, nums))` |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b181fc13",
   "metadata": {},
   "outputs": [],
   "source": [
    "# basic lambda \u2014 equivalent to def double(x): return x * 2\n",
    "double = lambda x: x * 2\n",
    "print(double(5))           # 10\n",
    "\n",
    "# multiple arguments\n",
    "add = lambda x, y: x + y\n",
    "print(add(3, 4))           # 7\n",
    "\n",
    "# inline \u2014 no assignment needed\n",
    "print((lambda x: x ** 2)(9))   # 81"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79d41aa5",
   "metadata": {},
   "outputs": [],
   "source": [
    "names = ['alice', 'bob', 'charlie', 'amy']\n",
    "nums  = [3, -1, 4, -1, 5, -9, 2, 6]\n",
    "\n",
    "# sorted(): sort by length\n",
    "by_length = sorted(names, key=lambda s: len(s))\n",
    "\n",
    "# map(): square every number\n",
    "squares = list(map(lambda x: x ** 2, nums))\n",
    "\n",
    "# filter(): keep only positive numbers\n",
    "positives = list(filter(lambda x: x > 0, nums))\n",
    "\n",
    "print(by_length)\n",
    "print(squares)\n",
    "print(positives)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cea69726",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-19T03:58:55.474503Z",
     "iopub.status.busy": "2026-03-19T03:58:55.474440Z",
     "iopub.status.idle": "2026-03-19T03:58:55.475720Z",
     "shell.execute_reply": "2026-03-19T03:58:55.475532Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Comprehension + Lambda\n",
    "# Given: numbers = [1, 2, 3, 4, 5, 6]\n",
    "# 1) Use a list comprehension to produce the squares of even numbers.\n",
    "# 2) Write a lambda that triples a value, then apply it to all numbers with map().\n",
    "# 3) Use filter() with a lambda to keep only numbers greater than 3.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c05b78e6",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-19T03:58:55.476680Z",
     "iopub.status.busy": "2026-03-19T03:58:55.476621Z",
     "iopub.status.idle": "2026-03-19T03:58:55.478501Z",
     "shell.execute_reply": "2026-03-19T03:58:55.478205Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[4, 16, 36]\n",
      "[2, 4, 6, 8, 10, 12]\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "numbers = [1, 2, 3, 4, 5, 6]\n",
    "\n",
    "# 1) squares of even numbers\n",
    "even_squares = [n ** 2 for n in numbers if n % 2 == 0]\n",
    "\n",
    "# 2) triple every number using lambda + map\n",
    "triple = lambda x: x * 3\n",
    "tripled = list(map(triple, numbers))\n",
    "\n",
    "# 3) keep only numbers greater than 3\n",
    "greater = list(filter(lambda x: x > 3, numbers))\n",
    "\n",
    "print(even_squares)   # [4, 16, 36]\n",
    "print(tripled)        # [3, 6, 9, 12, 15, 18]\n",
    "print(greater)        # [4, 5, 6]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ade40b1b",
   "metadata": {},
   "source": [
    "## Docstrings and `help()`\n",
    "\n",
    "A **docstring** is a string literal placed immediately after a `def` (or `class`) statement. Python attaches it to the object and `help()` displays it. It is the standard way to document what a function does, what it expects, and what it returns.\n",
    "\n",
    "```python\n",
    "def function_name(param):\n",
    "    \"\"\"One-line summary of what the function does.\n",
    "\n",
    "    Longer explanation if needed. Describe parameters and return value.\n",
    "\n",
    "    Args:\n",
    "        param: description of the parameter.\n",
    "\n",
    "    Returns:\n",
    "        description of the return value.\n",
    "    \"\"\"\n",
    "```\n",
    "\n",
    "Write a docstring for every function you intend to reuse or share. It costs almost nothing and saves significant time later."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "db16cf12",
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_tax(price, rate=0.07):\n",
    "    \"\"\"Return price with sales tax applied.\n",
    "\n",
    "    Args:\n",
    "        price: the pre-tax amount (float or int).\n",
    "        rate: tax rate as a decimal (default 0.07 = 7%).\n",
    "\n",
    "    Returns:\n",
    "        float: the post-tax amount.\n",
    "    \"\"\"\n",
    "    return price * (1 + rate)\n",
    "\n",
    "# help() reads the docstring\n",
    "help(add_tax)\n",
    "\n",
    "# __doc__ gives you the raw string\n",
    "print(add_tax.__doc__)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f37bfca",
   "metadata": {},
   "source": [
    "## Type Hints\n",
    "**Type hints** annotate function parameters, return values, and sometimes variables with their expected types. Python does not enforce them at runtime \u2014 they are documentation for readers and tools like linters, IDEs, and static type checkers.\n",
    "\n",
    "```python\n",
    "def greet(name: str) -> str:\n",
    "    return f\"Hello, {name}\"\n",
    "```\n",
    "\n",
    "| Annotation | Meaning |\n",
    "|---|---|\n",
    "| `x: int` | parameter `x` should be an integer |\n",
    "| `-> str` | the function returns a string |\n",
    "| `-> None` | the function returns nothing useful |\n",
    "| `x: list[int]` | a list of integers |\n",
    "| `x: dict[str, float]` | a dictionary with string keys and float values |\n",
    "| `x: int | None` | either an int or None |\n",
    "\n",
    "Use type hints for any module-level function you share with others. They act as a lightweight contract that makes code significantly easier to read and debug.\n",
    "\n",
    "### Collection Type Hints\n",
    "For collections, put the item type inside square brackets. This says not only that the value is a list, dictionary, tuple, or set, but also what kind of values it contains.\n",
    "\n",
    "```python\n",
    "scores: list[int] = [88, 92, 79]\n",
    "prices: dict[str, float] = {\"coffee\": 2.50, \"tea\": 2.00}\n",
    "point: tuple[float, float] = (3.5, 4.0)\n",
    "unique_ids: set[int] = {101, 102, 103}\n",
    "```\n",
    "\n",
    "### Optional Values and Unions\n",
    "Some functions may return either a normal value or `None`. In modern Python, write that with the union operator `|`:\n",
    "\n",
    "```python\n",
    "def find_score(name: str, scores: dict[str, int]) -> int | None:\n",
    "    return scores.get(name)\n",
    "```\n",
    "\n",
    "Read `int | None` as \"an integer or None.\" More generally, `A | B` means \"either type A or type B\":\n",
    "\n",
    "```python\n",
    "def parse_id(text: str) -> int | str:\n",
    "    if text.isdigit():\n",
    "        return int(text)\n",
    "    return text\n",
    "```\n",
    "\n",
    "Older Python code often uses names from the `typing` module:\n",
    "\n",
    "```python\n",
    "from typing import Optional, Union\n",
    "\n",
    "def find_score_old(name: str, scores: dict[str, int]) -> Optional[int]:\n",
    "    return scores.get(name)\n",
    "\n",
    "def parse_id_old(text: str) -> Union[int, str]:\n",
    "    if text.isdigit():\n",
    "        return int(text)\n",
    "    return text\n",
    "```\n",
    "\n",
    "`Optional[int]` means the same thing as `int | None`; `Union[int, str]` means the same thing as `int | str`.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3c4c9a96",
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_tax(price: float, rate: float = 0.07) -> float:\n",
    "    \"\"\"Return price with sales tax applied.\"\"\"\n",
    "    return price * (1 + rate)\n",
    "\n",
    "def first_word(sentence: str) -> str:\n",
    "    \"\"\"Return the first word of a sentence.\"\"\"\n",
    "    return sentence.split()[0]\n",
    "\n",
    "def repeat(items: list, n: int) -> list:\n",
    "    \"\"\"Return a new list with items repeated n times.\"\"\"\n",
    "    return items * n\n",
    "\n",
    "print(add_tax(49.99))\n",
    "print(first_word(\"hello world\"))\n",
    "print(repeat([1, 2], 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6be7c644",
   "metadata": {},
   "source": [
    "## `enumerate()` and `zip()`\n",
    "\n",
    "These two built-ins appear constantly in loops throughout the book.\n",
    "\n",
    "### `enumerate(iterable, start=0)`\n",
    "Returns `(index, value)` pairs. Use it whenever you need the position of an item while iterating \u2014 no manual counter variable needed.\n",
    "\n",
    "### `zip(iter1, iter2, ...)`\n",
    "Pairs up items from multiple iterables, stopping at the shortest. Use it to iterate two related sequences in lockstep \u2014 no index needed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c54fcc1a",
   "metadata": {},
   "outputs": [],
   "source": [
    "fruits = ['apple', 'banana', 'cherry']\n",
    "prices = [1.20, 0.50, 2.00]\n",
    "\n",
    "# enumerate \u2014 index + value\n",
    "for i, fruit in enumerate(fruits):\n",
    "    print(f\"{i}: {fruit}\")\n",
    "\n",
    "print()\n",
    "\n",
    "# enumerate with a custom start index\n",
    "for i, fruit in enumerate(fruits, start=1):\n",
    "    print(f\"{i}. {fruit}\")\n",
    "\n",
    "print()\n",
    "\n",
    "# zip \u2014 pair two lists\n",
    "for fruit, price in zip(fruits, prices):\n",
    "    print(f\"{fruit}: ${price:.2f}\")\n",
    "\n",
    "print()\n",
    "\n",
    "# zip into a dict\n",
    "price_map = dict(zip(fruits, prices))\n",
    "print(price_map)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a6baea4",
   "metadata": {},
   "source": [
    "## `try` / `except` \u2014 Error Handling\n",
    "\n",
    "When your code does something that might fail \u2014 opening a file, converting user input, accessing a key \u2014 wrap it in a `try` block so the program can recover gracefully instead of crashing.\n",
    "\n",
    "```python\n",
    "try:\n",
    "    # code that might raise an exception\n",
    "except SomeError:\n",
    "    # what to do when it fails\n",
    "else:\n",
    "    # runs only if no exception was raised (optional)\n",
    "finally:\n",
    "    # always runs, even if an exception occurred (optional)\n",
    "```\n",
    "\n",
    "**Catch specific exceptions** \u2014 avoid bare `except:` because it silently swallows every error including bugs.\n",
    "\n",
    "| Common exception | Triggered by |\n",
    "|---|---|\n",
    "| `ValueError` | Wrong type of value (`int(\"abc\")`) |\n",
    "| `TypeError` | Wrong type (`len(42)`) |\n",
    "| `KeyError` | Missing dict key |\n",
    "| `IndexError` | List index out of range |\n",
    "| `FileNotFoundError` | File doesn't exist |\n",
    "| `ZeroDivisionError` | Divide by zero |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "863ebcde",
   "metadata": {
    "tags": [
     "hide-input",
     "skip-execution"
    ]
   },
   "outputs": [],
   "source": [
    "def safe_divide(a: float, b: float) -> float | None:\n",
    "    \"\"\"Divide a by b; return None if b is zero.\"\"\"\n",
    "    try:\n",
    "        return a / b\n",
    "    except ZeroDivisionError:\n",
    "        print(\"Error: cannot divide by zero\")\n",
    "        return None\n",
    "\n",
    "print(safe_divide(10, 2))    # 5.0\n",
    "print(safe_divide(10, 0))    # Error message, then None\n",
    "\n",
    "# ValueError: converting invalid input\n",
    "def parse_int(text: str) -> int | None:\n",
    "    \"\"\"Convert text to int; return None on failure.\"\"\"\n",
    "    try:\n",
    "        return int(text)\n",
    "    except ValueError:\n",
    "        print(f\"Could not convert {text!r} to int\")\n",
    "        return None\n",
    "\n",
    "print(parse_int(\"42\"))       # 42\n",
    "print(parse_int(\"hello\"))    # error message, then None\n",
    "\n",
    "# FileNotFoundError: reading a missing file\n",
    "from pathlib import Path\n",
    "\n",
    "def read_file(path: str) -> str | None:\n",
    "    \"\"\"Read a file and return its contents, or None if file not found.\"\"\"\n",
    "    try:\n",
    "        return Path(path).read_text(encoding='utf-8')\n",
    "    except FileNotFoundError:\n",
    "        print(f\"File not found: {path}\")\n",
    "        return None\n",
    "\n",
    "print(read_file(\"nonexistent.txt\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db97e320",
   "metadata": {},
   "source": [
    "## f-String Formatting\n",
    "\n",
    "You have used f-strings for basic interpolation (`f\"Hello, {name}\"`). They support a rich **format spec** inside the braces that controls alignment, padding, decimal places, and thousands separators \u2014 useful for tables and reports throughout the book.\n",
    "\n",
    "```\n",
    "f\"{value:{width}.{precision}{type}}\"\n",
    "```\n",
    "\n",
    "| Spec | Meaning | Example | Output |\n",
    "|---|---|---|---|\n",
    "| `{x:10}` | right-pad to width 10 (strings left-align by default) | `f\"{'hi':10}!\"` | `hi        !` |\n",
    "| `{x:>10}` | right-align in 10 chars | `f\"{42:>10}` | `        42` |\n",
    "| `{x:<10}` | left-align | `f\"{42:<10}` | `42        ` |\n",
    "| `{x:^10}` | center | `f\"{'hi':^10}` | `    hi    ` |\n",
    "| `{x:.2f}` | float, 2 decimal places | `f\"{3.14159:.2f}\"` | `3.14` |\n",
    "| `{x:,.2f}` | float with thousands comma | `f\"{1234567.8:.2f}\"` | `1,234,567.80` |\n",
    "| `{x:08.2f}` | zero-padded, width 8 | `f\"{3.14:08.2f}\"` | `00003.14` |\n",
    "| `{x:e}` | scientific notation | `f\"{0.00012:.2e}\"` | `1.20e-04` |\n",
    "| `{x:%}` | percentage | `f\"{0.857:.1%}\"` | `85.7%` |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b1447608",
   "metadata": {},
   "outputs": [],
   "source": [
    "items = [\n",
    "    ('apple',   1.2,   842),\n",
    "    ('banana',  0.5,  3201),\n",
    "    ('cherry',  2.0,   150),\n",
    "]\n",
    "\n",
    "# aligned table using format specs\n",
    "print(f\"{'Item':<10} {'Price':>8} {'Sold':>8} {'Revenue':>12}\")\n",
    "print('-' * 42)\n",
    "for name, price, sold in items:\n",
    "    revenue = price * sold\n",
    "    print(f\"{name:<10} {price:>8.2f} {sold:>8,} {revenue:>12,.2f}\")\n",
    "\n",
    "print()\n",
    "\n",
    "# percentage and scientific notation\n",
    "score = 0.857\n",
    "tiny  = 0.000123\n",
    "print(f\"Accuracy : {score:.1%}\")\n",
    "print(f\"Epsilon  : {tiny:.2e}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "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.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
