Skip to content

Exceptions Handling

  • Concept: A mechanism to intercept runtime errors, prevent the program from crashing, and execute alternative logic or cleanup routines.
  • Methods & Syntax:
  • try: The block of code where you anticipate an error might occur.
  • except ExceptionType: The block that executes only if that specific error occurs.
  • else: Executes only if the try block succeeds without any exceptions.
  • finally: Executes always, regardless of success or failure. Strictly used for resource cleanup (e.g., closing files, dropping database connections).

Program:

def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
else:
# Executes only if division succeeded
print(f"Success! Result is {result}")
finally:
# Executes no matter what happened above
print("Execution of safe_divide completed.\\n")
safe_divide(10, 2) # Triggers 'else' and 'finally'
safe_divide(10, 0) # Triggers 'except' and 'finally'

  • Concept: You can target specific errors, group multiple errors together, and extract the system’s exact error message using an alias.
  • Methods & Syntax:
  • except ValueError as exc: Catches a ValueError and stores the error object in the variable exc.
  • except (Exception1, Exception2): Catches multiple specific exceptions in a single block using a tuple.
  • except Exception as e: The “Catch-All”. Catches almost any programmatic error. (Best practice: Put this at the very bottom of your except blocks as a last resort).

Program:

def temp_convert(var):
try:
return int(var)
except (TypeError, ValueError) as argument:
# Extracts the exact string reason the conversion failed
print(f"Conversion failed. System reason: {argument}")
temp_convert("xyz")
# Output:
# Conversion failed. System reason: invalid literal for int() with base 10: 'xyz'

  • Concept: You can manually halt your own program by throwing an error if a specific business logic condition is violated.
  • Methods & Syntax:
  • raise ExceptionName("Message"): Triggers an exception immediately.
  • raise NewException from old_exception: (Crucial Addition) Re-raises a new, clearer exception while preserving the original system traceback so you don’t lose the root cause during debugging.

Program:

def process_level(level):
if level < 1:
# Manually triggering a standard exception
raise ValueError(f"Invalid level! {level} is below minimum.")
print(f"Processing level {level}")
try:
# Simulating a database failure
1 / 0
except ZeroDivisionError as original_error:
# Exception Chaining: Translating a confusing system error into a clear business error
raise SystemError("Database connection failed during processing") from original_error
# Output of the chained error will show:
# ZeroDivisionError: division by zero
# The above exception was the direct cause of the following exception:
# SystemError: Database connection failed during processing

  • Concept: Standard Python exceptions (ValueError, TypeError) are often too generic for large applications. You create domain-specific errors by inheriting from Python’s base Exception class.
  • Methods & Syntax:
  • class CustomError(Exception): Create a class that inherits from Exception (or RuntimeError).
  • Add an __init__ method to accept custom error messages or error codes.

Program:

# Defining the Custom Exception
class NetworkError(Exception):
"""Raised when a network operation fails."""
def __init__(self, message, code):
self.message = message
self.code = code
# Call the base Exception constructor
super().__init__(self.message)
# Utilizing the Custom Exception
def connect_to_server(hostname):
if hostname == "bad_host":
raise NetworkError("Failed to resolve hostname", 404)
try:
connect_to_server("bad_host")
except NetworkError as e:
print(f"Network Alert! Code: {e.code}, Reason: {e.message}")
# Output:
# Network Alert! Code: 404, Reason: Failed to resolve hostname