{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "9a098d66",
   "metadata": {},
   "source": [
    "# Coding Patterns and Idioms\n",
    "\n",
    "This notebook is a **recipe reference** — 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"
   ]
  },
  {
   "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** — 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 — 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 — 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 — 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 — 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 — 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 — 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 — 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` — Error Handling\n",
    "\n",
    "When your code does something that might fail — opening a file, converting user input, accessing a key — 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** — 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": {},
   "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 — 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
}
