Modules and Packages - Code Organization

Modules are like chapters in a book, and packages are like books in a library. Instead of writing everything in one huge file, you organize code into logical units that can be reused.
As your Python programs grow, keeping all code in a single file becomes messy and hard to maintain. **Modules** and **packages** help you organise code into reusable, manageable pieces.

## Modules

A **module** is simply a Python file (`.py`) containing functions, classes, or variables. You can import a module into another file using the `import` statement.

**Why use modules?**
- **Reusability** – write code once, use it in multiple projects.
- **Organisation** – group related functions together (e.g., math operations in `math_utils.py`).
- **Namespace** – avoid name collisions (two modules can have a function named `calculate`).
- **Maintainability** – easier to debug and update smaller files.

**Importing modules:**
```python
import math # import entire module
math.sqrt(16) # access with dot notation

from math import sqrt, pi # import specific items
sqrt(16) # use directly

from math import * # import all (not recommended – pollutes namespace)

import math as m # alias (shortcut)
m.sqrt(16)
```

**Built‑in modules (Python Standard Library):**
Python comes with many built‑in modules – no installation needed. Examples:
- `math` – mathematical functions (`sqrt`, `sin`, `pi`).
- `random` – generate random numbers (`randint`, `choice`).
- `datetime` – dates and times.
- `os` – operating system interface.
- `sys` – system‑specific parameters.
- `json` – JSON data handling.
- `re` – regular expressions.

**Creating your own module:**
1. Create a `.py` file (e.g., `mymodule.py`).
2. Write functions, classes, or variables.
3. In another file, use `import mymodule` or `from mymodule import function`.

## Packages

A **package** is a folder that contains multiple modules. It must include an `__init__.py` file (can be empty) to tell Python it's a package.

**Package structure:**
```
my_package/
__init__.py
math_utils.py
string_utils.py
data_processing.py
```

**Importing from packages:**
```python
import my_package.math_utils
from my_package import string_utils
from my_package.data_processing import process_data
```

## Third‑party packages (pip)

Not all modules are built‑in. You can install external packages using `pip` (Python's package installer).
```bash
pip install package_name
pip install pandas numpy requests
```

**Popular third‑party packages:**
- `numpy` – numerical computing.
- `pandas` – data analysis.
- `requests` – HTTP requests.
- `flask` – web development.
- `beautifulsoup4` – web scraping.

## Virtual environments

A **virtual environment** isolates dependencies for different projects. It prevents version conflicts (e.g., Project A needs Django 3.0, Project B needs Django 4.0).
```bash
python -m venv myenv # create
source myenv/bin/activate # activate (Mac/Linux)
myenv\Scripts\activate # activate (Windows)
deactivate # deactivate
```

## Best practices
- Place all imports at the top of the file.
- Use `import module` rather than `from module import *` (clearer where functions come from).
- Use aliases for long module names (`import numpy as np`).
- Document your modules with docstrings.
- Keep modules focused on a single responsibility.

## Common mistakes
- Naming your module the same as a built‑in module (e.g., `math.py`).
- Circular imports (Module A imports B, B imports A).
- Forgetting to include `__init__.py` in a package (prior to Python 3.3).
- Installing packages system‑wide instead of in a virtual environment.

## Practice exercises
1. Create a module `greetings.py` with a function `say_hello(name)`. Import and use it in another script.
2. Write a module `stats.py` with functions `mean()`, `median()`, `mode()`. Test it.
3. Use the `random` module to simulate rolling a six‑sided die 100 times and count how many times each face appears.
4. Install the `requests` package and fetch data from a public API (e.g., `https://api.github.com`).
5. Create a package `shapes` with modules `circle.py` (area/perimeter of circle) and `rectangle.py`. Import them in a main script.

**Real‑world uses:**
- Reusing database connection logic across multiple scripts.
- Sharing utility functions (date formatting, string cleaning) across a team.
- Organising a web application into controllers, models, and views.
- Using popular libraries to avoid reinventing the wheel (e.g., `pandas` for data frames).
# ========== EXAMPLE 1: Importing a Whole Module (math) ==========
print("=== Example 1: Import Whole Module ===")
import math
print(f"Square root of 16: {math.sqrt(16)}")
print(f"Pi: {math.pi}")
print(f"Factorial of 5: {math.factorial(5)}")
print()

# ========== EXAMPLE 2: Importing Specific Functions ==========
print("=== Example 2: Import Specific Functions ===")
from math import sqrt, pi, factorial
print(f"sqrt(25) = {sqrt(25)}")
print(f"Pi = {pi}")
print(f"factorial(5) = {factorial(5)}")
print()

# ========== EXAMPLE 3: Using Aliases (as) ==========
print("=== Example 3: Module Aliasing ===")
import random as rnd
import datetime as dt
print(f"Random number (1-10): {rnd.randint(1, 10)}")
print(f"Current date/time: {dt.datetime.now()}")
print()

# ========== EXAMPLE 4: The random Module – Dice Roll Simulation ==========
print("=== Example 4: Random Module – Dice Roll ===")
import random
rolls = [random.randint(1, 6) for _ in range(10)]
print(f"10 dice rolls: {rolls}")
print(f"Average: {sum(rolls)/len(rolls):.2f}")
print()

# ========== EXAMPLE 5: The statistics Module ==========
print("=== Example 5: Statistics Module ===")
import statistics
data = [12, 15, 14, 10, 18, 20, 15]
print(f"Data: {data}")
print(f"Mean: {statistics.mean(data)}")
print(f"Median: {statistics.median(data)}")
print(f"Standard deviation: {statistics.stdev(data):.2f}")
print()

# ========== EXAMPLE 6: Creating and Using a Custom Module (simulated) ==========
print("=== Example 6: Custom Module (Simulated) ===")
# In real code, save this as mymodule.py and import it
# Here we define functions inline for demonstration
def greet(name):
    return f"Hello, {name}!"
def add(a, b):
    return a + b
def is_even(n):
    return n % 2 == 0

print(greet("Alice"))
print(f"5 + 3 = {add(5, 3)}")
print(f"Is 7 even? {is_even(7)}")
print()

# ========== EXAMPLE 7: The datetime Module – Age Calculator ==========
print("=== Example 7: Datetime Module – Age Calculator ===")
from datetime import date
today = date.today()
birth = date(1995, 8, 15)
age = today.year - birth.year - ((today.month, today.day) < (birth.month, birth.day))
print(f"Birthday: {birth}")
print(f"Today: {today}")
print(f"Age: {age} years")
print()

# ========== EXAMPLE 8: The os Module – File and System Information ==========
print("=== Example 8: OS Module ===")
import os
print(f"Current working directory: {os.getcwd()}")
print(f"Operating system name: {os.name}")
print(f"List files in current dir: {os.listdir('.')[:3]}...")  # first 3 only
print()

# ========== EXAMPLE 9: Package Structure (Conceptual) ==========
print("=== Example 9: Package Structure ===")
print("Example package layout:")
print("""
my_package/
    __init__.py
    math_utils.py
    string_utils.py
""")
print("Import from package:")
print("from my_package import math_utils")
print("from my_package.string_utils import reverse_string")
print()

# ========== EXAMPLE 10: Virtual Environments and pip (Instructions) ==========
print("=== Example 10: Virtual Environments and pip ===")
print("Creating a virtual environment:")
print("  python -m venv myenv")
print("\nActivating (Mac/Linux):")
print("  source myenv/bin/activate")
print("\nActivating (Windows):")
print("  myenv\\Scripts\\activate")
print("\nInstalling a package:")
print("  pip install requests")
print("\nUsing the installed package:")
print("  import requests")
print("  response = requests.get('https://api.github.com')")
print("\nDeactivate:")
print("  deactivate")

→ Run this code interactively