# 174. Exception Hierarchy and Inheritance

**Snippet 1: Viewing Python's Exception Hierarchy**

```python
import inspect

for cls in inspect.getmro(Exception):
    print(cls.__name__)

# Output:
# Exception
# BaseException
# object
```

***

**Snippet 2: Basic Try-Except with Built-in Exceptions**

```python
try:
    x = 10 / 0
except ZeroDivisionError as e:
    print(f"Caught an exception: {e}")
```

***

**Snippet 3: Catching Multiple Exceptions**

```python
try:
    x = int("abc")
except (ValueError, TypeError) as e:
    print(f"Caught an exception: {e}")
```

***

**Snippet 4: Raising a Built-in Exception**

```python
def divide(a, b):
    if b == 0:
        raise ZeroDivisionError("Division by zero is not allowed!")
    return a / b

try:
    divide(10, 0)
except ZeroDivisionError as e:
    print(f"Error: {e}")
```

***

**Snippet 5: Creating a Custom Exception**

```python
class CustomError(Exception):
    """Custom exception class for demonstration."""
    pass

try:
    raise CustomError("This is a custom error!")
except CustomError as e:
    print(f"Caught a custom exception: {e}")
```

***

**Snippet 6: Custom Exception with Additional Attributes**

```python
class ValidationError(Exception):
    def __init__(self, message, field):
        super().__init__(message)
        self.field = field

try:
    raise ValidationError("Invalid input", "username")
except ValidationError as e:
    print(f"Error: {e} (Field: {e.field})")
```

***

**Snippet 7: Using Inheritance for Custom Exceptions**

```python
class AppError(Exception):
    """Base class for application errors."""
    pass

class DatabaseError(AppError):
    """Raised for database-related errors."""
    pass

class NetworkError(AppError):
    """Raised for network-related errors."""
    pass

try:
    raise DatabaseError("Unable to connect to the database.")
except AppError as e:
    print(f"Application error: {e}")
```

***

**Snippet 8: Catching and Reraising Exceptions**

```python
try:
    try:
        x = int("abc")
    except ValueError as e:
        print(f"Caught an exception: {e}")
        raise
except Exception as e:
    print(f"Reraised exception: {e}")
```

***

**Snippet 9: Using `__cause__` for Exception Chaining**

```python
try:
    try:
        raise ValueError("Invalid value")
    except ValueError as e:
        raise TypeError("Type mismatch") from e
except TypeError as e:
    print(f"Error: {e}")
    print(f"Caused by: {e.__cause__}")
```

***

**Snippet 10: Ensuring Cleanup with `finally`**

```python
try:
    x = 10 / 0
except ZeroDivisionError:
    print("Caught ZeroDivisionError")
finally:
    print("Cleanup code runs regardless of exceptions.")
```

***
