{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Python — Next Steps\n",
    "\n",
    "Picks up where **`python_basics.ipynb`** left off. Five short topics that take you from \"I can read Python\" to \"I can write small useful scripts.\"\n",
    "\n",
    "**Sections**\n",
    "1. Modules — using code other people wrote\n",
    "2. File I/O — reading and writing text files\n",
    "3. Error handling — `try` / `except`\n",
    "4. List comprehensions — building lists in one line\n",
    "5. Magic names — the words with `__` on both sides\n",
    "6. Mini exercises\n",
    "\n",
    "Run cells with **Shift+Enter**. Break things, fix them, learn more.\n",
    "\n",
    "**Every section ends with an exercise and a worked solution.** Try the exercise before you open the solution — that is where the learning happens.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Modules\n",
    "\n",
    "Python ships with hundreds of **modules** — collections of useful functions you can `import`. Two everyday ones: `math` and `random`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "print(math.sqrt(16))      # 4.0\n",
    "print(math.pi)            # 3.1415...\n",
    "print(math.floor(3.7))    # 3\n",
    "print(math.ceil(3.2))     # 4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "\n",
    "print(random.random())                   # float between 0 and 1\n",
    "print(random.randint(1, 10))             # integer 1..10 inclusive\n",
    "print(random.choice([\"mango\", \"durian\", \"longan\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Three import styles — pick whichever reads best."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math                  # use as math.sqrt(...)\n",
    "from math import sqrt        # use as sqrt(...) directly\n",
    "import math as m             # use as m.sqrt(...)\n",
    "\n",
    "print(math.sqrt(9), sqrt(9), m.sqrt(9))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some modules ship with Python (the **standard library** — `math`, `random`, `json`, `os`, `datetime`...). Others you install with `pip install name`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Exercise 1 — modules\n",
    "\n",
    "Using `random` and `math`:\n",
    "\n",
    "1. Roll two dice (each a random whole number from 1 to 6) and print the total.\n",
    "2. Print the square root of 144.\n",
    "3. Pick a random city from `[\"Chiang Mai\", \"Lampang\", \"Pai\"]`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Your code here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Solution 1** — try it yourself first, then run this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "import math\n",
    "\n",
    "die1 = random.randint(1, 6)\n",
    "die2 = random.randint(1, 6)\n",
    "print(f\"{die1} + {die2} = {die1 + die2}\")\n",
    "\n",
    "print(math.sqrt(144))     # 12.0\n",
    "\n",
    "print(random.choice([\"Chiang Mai\", \"Lampang\", \"Pai\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. File I/O\n",
    "\n",
    "Reading and writing text files. The `with` statement makes sure the file is properly closed even if something goes wrong."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Write a small file\n",
    "with open(\"notes.txt\", \"w\", encoding=\"utf-8\") as f:\n",
    "    f.write(\"Chiang Mai\\n\")\n",
    "    f.write(\"Lampang\\n\")\n",
    "    f.write(\"Chiang Rai\\n\")\n",
    "\n",
    "print(\"Wrote notes.txt\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Read it back, whole file at once\n",
    "with open(\"notes.txt\", encoding=\"utf-8\") as f:\n",
    "    text = f.read()\n",
    "\n",
    "print(text)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Or read line by line (better for large files)\n",
    "with open(\"notes.txt\", encoding=\"utf-8\") as f:\n",
    "    for line in f:\n",
    "        print(line.strip())   # .strip() removes the trailing newline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**File modes** — the second argument to `open()`:\n",
    "\n",
    "| Mode | Meaning                              |\n",
    "|------|--------------------------------------|\n",
    "| `\"r\"` | read (default)                      |\n",
    "| `\"w\"` | write — **erases the file first**   |\n",
    "| `\"a\"` | append — adds to the end             |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Append a fourth city\n",
    "with open(\"notes.txt\", \"a\", encoding=\"utf-8\") as f:\n",
    "    f.write(\"Mae Hong Son\\n\")\n",
    "\n",
    "with open(\"notes.txt\", encoding=\"utf-8\") as f:\n",
    "    print(f.read())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Exercise 2 — file I/O\n",
    "\n",
    "1. Write a file `students.txt` containing three names, one per line.\n",
    "2. Read it back and print each name with its line number, like `1. Ploy`.\n",
    "\n",
    "Remember `encoding=\"utf-8\"` and `.strip()` to remove the newline."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Your code here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Solution 2** — try it yourself first, then run this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "names = [\"Ploy\", \"Aroon\", \"Nong Mee\"]\n",
    "\n",
    "with open(\"students.txt\", \"w\", encoding=\"utf-8\") as f:\n",
    "    for name in names:\n",
    "        f.write(name + \"\\n\")\n",
    "\n",
    "with open(\"students.txt\", encoding=\"utf-8\") as f:\n",
    "    for number, line in enumerate(f, start=1):\n",
    "        print(f\"{number}. {line.strip()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Error handling\n",
    "\n",
    "When something goes wrong, Python **raises an exception** and the program stops. Run the cell below — it will fail on purpose."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This will raise ZeroDivisionError\n",
    "result = 10 / 0\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Read the **traceback** above carefully — the last line names the exception type (`ZeroDivisionError`). To handle it gracefully, wrap the risky code in `try` / `except`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    result = 10 / 0\n",
    "    print(result)\n",
    "except ZeroDivisionError:\n",
    "    print(\"Cannot divide by zero!\")\n",
    "\n",
    "print(\"Program keeps running\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Catch different exceptions differently. Use `as e` to inspect the exception object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "student = {\"name\": \"Ploy\", \"age\": 12}\n",
    "\n",
    "try:\n",
    "    print(student[\"score\"])           # missing key\n",
    "except KeyError as e:\n",
    "    print(f\"No such key: {e}\")\n",
    "except Exception as e:\n",
    "    print(f\"Some other problem: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`finally` always runs — useful for cleanup. You can also `raise` your own exceptions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def divide(a, b):\n",
    "    if b == 0:\n",
    "        raise ValueError(\"b must not be zero\")\n",
    "    return a / b\n",
    "\n",
    "try:\n",
    "    print(divide(10, 2))\n",
    "    print(divide(10, 0))\n",
    "except ValueError as e:\n",
    "    print(f\"Caught: {e}\")\n",
    "finally:\n",
    "    print(\"This runs no matter what\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Rule of thumb:** only catch exceptions you actually know how to handle. Catching `Exception` and silently ignoring it hides real bugs."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Exercise 3 — error handling\n",
    "\n",
    "Write a function `safe_divide(a, b)` that returns `a / b`, but returns the\n",
    "string `\"Cannot divide by zero\"` instead of crashing when `b` is `0`.\n",
    "\n",
    "Test it with `safe_divide(10, 2)` and `safe_divide(10, 0)`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Your code here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Solution 3** — try it yourself first, then run this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def safe_divide(a, b):\n",
    "    \"\"\"Divide a by b, or explain why we cannot.\"\"\"\n",
    "    try:\n",
    "        return a / b\n",
    "    except ZeroDivisionError:\n",
    "        return \"Cannot divide by zero\"\n",
    "\n",
    "print(safe_divide(10, 2))    # 5.0\n",
    "print(safe_divide(10, 0))    # Cannot divide by zero"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. List comprehensions\n",
    "\n",
    "A concise way to build a list. Read it as: \"the *expression* for each *item* in *iterable*\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The long way\n",
    "squares = []\n",
    "for n in range(10):\n",
    "    squares.append(n * n)\n",
    "print(squares)\n",
    "\n",
    "# The comprehension way\n",
    "squares = [n * n for n in range(10)]\n",
    "print(squares)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Add an `if` to filter:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "evens = [n for n in range(20) if n % 2 == 0]\n",
    "print(evens)\n",
    "\n",
    "cities = [\"Chiang Mai\", \"Lampang\", \"Mae Hong Son\", \"Pai\"]\n",
    "short = [c for c in cities if len(c) < 8]\n",
    "print(short)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The same syntax works for **dict** and **set** comprehensions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Dict: map city -> name length\n",
    "lengths = {c: len(c) for c in cities}\n",
    "print(lengths)\n",
    "\n",
    "# Set: unique first letters\n",
    "letters = {c[0] for c in cities}\n",
    "print(letters)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**When *not* to use them:** if the expression or condition is long, write a normal `for` loop. Comprehensions are for clarity; if yours needs a comment to read, it's gone too far."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Exercise 4 — list comprehensions\n",
    "\n",
    "Start with `temps_c = [28, 35, 19, 31, 24]` (degrees Celsius).\n",
    "\n",
    "1. Build a list of the same temperatures in Fahrenheit: `c * 9 / 5 + 32`.\n",
    "2. Build a list of only the Celsius temperatures above 25.\n",
    "3. Build a dictionary mapping each Celsius value to its Fahrenheit value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Your code here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Solution 4** — try it yourself first, then run this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "temps_c = [28, 35, 19, 31, 24]\n",
    "\n",
    "fahrenheit = [c * 9 / 5 + 32 for c in temps_c]\n",
    "print(fahrenheit)\n",
    "\n",
    "hot = [c for c in temps_c if c > 25]\n",
    "print(hot)\n",
    "\n",
    "both = {c: c * 9 / 5 + 32 for c in temps_c}\n",
    "print(both)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Magic names\n",
    "\n",
    "Some names in Python have two underscores on each side: `__name__`, `__init__`, `__file__`.\n",
    "\n",
    "People say **dunder** — short for **d**ouble **under**score. So `__init__` is said out loud as\n",
    "\"dunder init\".\n",
    "\n",
    "They are not really magic. They are ordinary names that Python has already promised to use for\n",
    "one particular job. The important rule:\n",
    "\n",
    "> **You almost never call them yourself. Python calls them for you, at the moment it needs them.**\n",
    "\n",
    "You write `len(basket)` and Python calls `basket.__len__()`. You write `print(dog)` and Python\n",
    "calls `dog.__str__()`. You never type the dunder name at the call site."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `__name__` — who is running this file?\n",
    "\n",
    "Every Python file has a `__name__`. When you run a file yourself, Python sets its `__name__` to\n",
    "the string `\"__main__\"`. When another file imports it, `__name__` is the module's name instead.\n",
    "\n",
    "That is the whole reason for the last two lines of every script you have written:\n",
    "\n",
    "```python\n",
    "if __name__ == \"__main__\":\n",
    "    main()\n",
    "```\n",
    "\n",
    "It means *only run `main()` if I am the file being started*. It is what stops your game opening\n",
    "a window when some other file just wants to import one function from it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(__name__)\n",
    "\n",
    "# In a notebook and in a script you run, this is \"__main__\".\n",
    "# Inside an imported module it would be the module's name, like \"space_invaders\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `__init__` and friends — making your class work with ordinary Python\n",
    "\n",
    "This is the useful part. Write these methods and your own class starts behaving like a built-in\n",
    "type: `print()` works on it, `len()` works on it, `in` works on it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Basket:\n",
    "    \"\"\"A basket that holds fruit.\"\"\"\n",
    "\n",
    "    def __init__(self, fruits):        # Python calls this when you make one\n",
    "        self.fruits = fruits\n",
    "\n",
    "    def __str__(self):                 # Python calls this for print()\n",
    "        return f\"Basket of {len(self.fruits)} fruits\"\n",
    "\n",
    "    def __len__(self):                 # Python calls this for len()\n",
    "        return len(self.fruits)\n",
    "\n",
    "    def __contains__(self, item):      # Python calls this for \"in\"\n",
    "        return item in self.fruits\n",
    "\n",
    "\n",
    "my_basket = Basket([\"mango\", \"banana\", \"longan\"])\n",
    "\n",
    "print(my_basket)                # Basket of 3 fruits     <- __str__\n",
    "print(len(my_basket))           # 3                      <- __len__\n",
    "print(\"mango\" in my_basket)     # True                   <- __contains__\n",
    "print(Basket.__doc__)           # A basket that holds fruit."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Take `__str__` out and `print(my_basket)` gives you something like\n",
    "`<__main__.Basket object at 0x104a2f9d0>`. That is Python's last resort when you have not told\n",
    "it how your object should look."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Plain:\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "\n",
    "\n",
    "print(Plain(\"no dunder str\"))   # ugly, but this is what you get by default"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `__file__` — where am I on the disk?\n",
    "\n",
    "`__file__` holds the path of the file that is running. The space game uses it to find its\n",
    "pictures, because it must work no matter which folder you start it from:\n",
    "\n",
    "```python\n",
    "IMAGE_DIR = Path(__file__).resolve().parent / \"images\"\n",
    "```\n",
    "\n",
    "Notebooks are a special case: a notebook is not a plain `.py` file on disk, so `__file__` is\n",
    "often not set. Run the cell below and see."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    print(__file__)\n",
    "except NameError:\n",
    "    print(\"__file__ is not set in this notebook.\")\n",
    "    print(\"In a .py script it holds the path to that script.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The ones worth knowing\n",
    "\n",
    "| Magic name | Python calls it when you write | You get |\n",
    "|---|---|---|\n",
    "| `__init__` | `Dog(\"Rex\")` | a new object, set up |\n",
    "| `__str__` | `print(dog)` | text a person can read |\n",
    "| `__len__` | `len(dog)` | a number |\n",
    "| `__contains__` | `\"ball\" in dog` | `True` or `False` |\n",
    "| `__eq__` | `dog1 == dog2` | `True` or `False` |\n",
    "| `__doc__` | *(you read it)* | the docstring |\n",
    "| `__name__` | *(you read it)* | `\"__main__\"`, or the module name |\n",
    "| `__file__` | *(you read it)* | the path of this file |\n",
    "\n",
    "**Do not invent your own.** Names like `__mything__` are reserved for Python itself, and a future\n",
    "version may give yours a meaning you did not want. If you want a private helper, one leading\n",
    "underscore is the convention: `_helper`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# dir() lists every name an object has. Most of them are dunders you never call.\n",
    "print([name for name in dir(my_basket) if name.startswith(\"__\")][:12])\n",
    "print()\n",
    "print([name for name in dir(my_basket) if not name.startswith(\"__\")])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ✏️ Exercise 5 — magic names\n",
    "\n",
    "Write a class `Playlist`:\n",
    "\n",
    "1. `__init__` takes a list of song names and stores it.\n",
    "2. `__str__` returns something like `Playlist: 3 songs`.\n",
    "3. `__len__` returns how many songs there are.\n",
    "4. `__contains__` says whether a song is in the playlist.\n",
    "\n",
    "Then test all four with `print()`, `len()` and `in`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Your code here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Solution 5** — try it yourself first, then run this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Playlist:\n",
    "    \"\"\"A list of songs.\"\"\"\n",
    "\n",
    "    def __init__(self, songs):\n",
    "        self.songs = songs\n",
    "\n",
    "    def __str__(self):\n",
    "        return f\"Playlist: {len(self.songs)} songs\"\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.songs)\n",
    "\n",
    "    def __contains__(self, song):\n",
    "        return song in self.songs\n",
    "\n",
    "\n",
    "my_list = Playlist([\"Loy Krathong\", \"Sabai Sabai\", \"Khon Jai Ngai\"])\n",
    "\n",
    "print(my_list)                        # Playlist: 3 songs\n",
    "print(len(my_list))                   # 3\n",
    "print(\"Sabai Sabai\" in my_list)       # True\n",
    "print(\"Yesterday\" in my_list)         # False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Mini exercises\n",
    "\n",
    "1. Use `random.choice` to pick a random city from `[\"Chiang Mai\", \"Lampang\", \"Pai\", \"Mae Hong Son\"]`, then print it 5 times (so you see the randomness).\n",
    "2. Write a function `safe_int(s)` that returns `int(s)` if it works, or `None` if the string isn't a valid integer. (Hint: `ValueError`.)\n",
    "3. Given `prices = [45, 120, 30, 200, 75]`, use a list comprehension to build a list of prices in USD assuming 1 USD = 35 THB. Round to 2 decimals."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise 1: your code here\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise 2: your code here\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Exercise 3: your code here\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "### Solutions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1\n",
    "import random\n",
    "cities = [\"Chiang Mai\", \"Lampang\", \"Pai\", \"Mae Hong Son\"]\n",
    "for _ in range(5):\n",
    "    print(random.choice(cities))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 2\n",
    "def safe_int(s):\n",
    "    try:\n",
    "        return int(s)\n",
    "    except ValueError:\n",
    "        return None\n",
    "\n",
    "print(safe_int(\"42\"))      # 42\n",
    "print(safe_int(\"hello\"))   # None\n",
    "print(safe_int(\"3.14\"))    # None (int() rejects decimals in strings)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 3\n",
    "prices = [45, 120, 30, 200, 75]\n",
    "in_usd = [round(p / 35, 2) for p in prices]\n",
    "print(in_usd)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Where to go after this\n",
    "\n",
    "- **classes** — `class Dog: ...` for grouping data + behavior together\n",
    "- **generators** — `yield` for lazy sequences that don't fit in memory\n",
    "- **decorators** — `@something` for wrapping functions\n",
    "- **type hints** — `def greet(name: str) -> str:` for better tooling and clarity\n",
    "- **`pathlib`** — modern file-path handling that beats `os.path`\n",
    "- **`json`** — read/write JSON files in two lines\n",
    "- **`requests`** (pip install) — fetch web pages and APIs"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
