# Chapter 9: Error and Exception Handling

Errors and exceptions are inevitable in programming. Python provides a robust mechanism for handling errors gracefully to ensure programs can recover and continue execution where possible.

#### Types of Errors

1. **Syntax Errors**:
   * Occur when the code violates Python’s grammar rules.
   * Example:

     ```python
     print("Hello")  # Correct
     print("Hello"    # SyntaxError: unexpected EOF while parsing
     ```
2. **Runtime Errors**:
   * Occur during the execution of a program.
   * Example:

     ```python
     result = 10 / 0  # ZeroDivisionError
     ```
3. **Logical Errors**:
   * Errors in the program’s logic that produce incorrect results without causing crashes.
   * Example:

     ```python
     # Intended to calculate the average
     total = 100
     count = 0
     average = total / count  # ZeroDivisionError
     ```

#### Handling Exceptions

Python uses `try...except` blocks to handle exceptions.

**Syntax:**

```python
try:
    # Code that might raise an exception
except ExceptionType:
    # Code to handle the exception
finally:
    # Code that executes regardless of an exception (optional)
```

**Example:**

```python
try:
    num = int(input("Enter a number: "))
    result = 10 / num
    print("Result:", result)
except ZeroDivisionError:
    print("Cannot divide by zero.")
except ValueError:
    print("Invalid input. Please enter a number.")
finally:
    print("Execution completed.")
```

#### The `else` Clause

The `else` block executes if no exceptions are raised in the `try` block.

**Example:**

```python
try:
    num = int(input("Enter a number: "))
    print("You entered:", num)
except ValueError:
    print("That’s not a valid number.")
else:
    print("Success! No errors occurred.")
```

#### Raising Exceptions

Use the `raise` keyword to generate exceptions intentionally.

**Example:**

```python
def validate_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative.")
    print("Valid age.")

try:
    validate_age(-5)
except ValueError as e:
    print("Error:", e)
```

#### Creating Custom Exceptions

You can define your own exceptions by subclassing the built-in `Exception` class.

**Example:**

```python
class NegativeNumberError(Exception):
    def __init__(self, value):
        self.value = value

try:
    num = int(input("Enter a positive number: "))
    if num < 0:
        raise NegativeNumberError(num)
except NegativeNumberError as e:
    print(f"Negative numbers are not allowed: {e.value}")
```

#### Common Built-in Exceptions

| **Exception**       | **Description**                                                    |
| ------------------- | ------------------------------------------------------------------ |
| `ValueError`        | Raised when a function receives an invalid argument.               |
| `TypeError`         | Raised when an operation is performed on an inappropriate type.    |
| `ZeroDivisionError` | Raised when dividing by zero.                                      |
| `KeyError`          | Raised when a key is not found in a dictionary.                    |
| `IndexError`        | Raised when an index is out of range.                              |
| `FileNotFoundError` | Raised when a file operation fails because the file doesn’t exist. |

**Example:**

```python
try:
    my_dict = {"name": "Alice"}
    print(my_dict["age"])
except KeyError:
    print("Key not found.")
```

#### Logging Exceptions

Use the `logging` module to record errors and exceptions for debugging and analysis.

**Example:**

```python
import logging

logging.basicConfig(filename="app.log", level=logging.ERROR)

try:
    result = 10 / 0
except ZeroDivisionError as e:
    logging.error("Exception occurred", exc_info=True)
```

#### Exercises

**Exercise 1:**

Write a program to take two numbers as input and perform division. Handle `ZeroDivisionError` and `ValueError` exceptions.

**Solution:**

```python
try:
    num1 = int(input("Enter numerator: "))
    num2 = int(input("Enter denominator: "))
    result = num1 / num2
    print("Result:", result)
except ZeroDivisionError:
    print("Error: Cannot divide by zero.")
except ValueError:
    print("Error: Invalid input. Please enter numbers only.")
```

**Exercise 2:**

Create a custom exception `OutOfRangeError` that is raised when a number is not between 1 and 100.

**Solution:**

```python
class OutOfRangeError(Exception):
    pass

try:
    num = int(input("Enter a number between 1 and 100: "))
    if num < 1 or num > 100:
        raise OutOfRangeError("Number out of range.")
    print("Valid number.")
except OutOfRangeError as e:
    print("Error:", e)
```

**Exercise 3:**

Write a program that logs errors to a file instead of printing them to the console.

**Solution:**

```python
import logging

logging.basicConfig(filename="errors.log", level=logging.ERROR)

try:
    num = int(input("Enter a number: "))
    result = 10 / num
except Exception as e:
    logging.error("Error occurred: %s", e)
    print("An error occurred. Check errors.log for details.")
```

#### Best Practices

1. Use specific exception types instead of a generic `except` clause.
2. Keep the `try` block small and focused.
3. Log exceptions for debugging and maintenance.
4. Avoid using exceptions for normal control flow.
5. Use custom exceptions to represent specific error cases in your application.

In the next chapter, we will explore working with databases using Python, including basic CRUD operations and interacting with `sqlite3`.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://py.d19.in/chapter-9-error-and-exception-handling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
