What are Errors?¶
When running a Python program, errors can occur due to:
- Typographical or syntactic mistakes (e.g., missing colons, parentheses)
- Invalid operations during execution (e.g., dividing by zero, accessing undefined variables)
Python categorizes errors into two major types:
| Type | Description | Example |
|---|---|---|
| Syntax Errors(Parsing error) | Detected by the Python parser before code execution. | Missing colon in if x == 1 print(x) |
| Exceptions | Detected during program execution. | Dividing by zero or accessing a missing variable |
Syntax Errors¶
A syntax error (or parsing error) occurs when Python fails to interpret your code because it violates grammatical rules.
When the parser encounters a syntax error, it stops execution immediately and displays an error message without running any part of the program.
Typical Python syntax errors include:
- Improper or inconsistent indentation
- Missing punctuation such as colons, commas, or brackets
- Misplacement or incorrect use of reserved keywords
# Missing colon at the end of 'while True'
while True print('Intensity Coding')
File "/tmp/ipython-input-672742266.py", line 2 while True print('Intensity Coding') ^ SyntaxError: invalid syntax
- The caret (^) points to the spot where Python noticed something wrong. Often, the real problem is a few characters before the marked position.
Exceptions¶
- Even if your code is syntactically correct, runtime errors can still occur. These are known as exceptions or Logical error.
# Common runtime errors
print(10 * (1/0)) # ZeroDivisionError
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) /tmp/ipython-input-2217227179.py in <cell line: 0>() 1 # Common runtime errors ----> 2 print(10 * (1/0)) # ZeroDivisionError ZeroDivisionError: division by zero
- This line raises a ZeroDivisionError because dividing by zero is undefined.
- Each exception has:
- A type (e.g., ZeroDivisionError)
- A message describing what went wrong
- A traceback, which shows the chain of calls leading to the error
- If such errors aren’t handled, Python will stop the execution immediately. That’s where try-except blocks come into play.
What is an Exception?¶
An exception is an unexpected event that occurs during the execution of a program, interrupting its normal flow.
When a Python program encounters a situation it cannot process (like dividing by zero or referencing an undefined variable), it raises an exception a special Python object that signals the presence of an error.
Instead of letting the program crash, Python provides structured ways to handle these errors gracefully using the
try-exceptmechanism.Exception handling prevents programs from crashing and allows custom error messages.
In Python, an exception is an object that inherits from the BaseException class and stores details about an error that occurs during program execution. Each exception object holds:
- The type of error (name of the exception)
- The program’s state at the moment the error occurred
- A descriptive message explaining the cause of the error
Exceptions help identify and handle various types of runtime failures. In Python, exceptions can be raised within a
tryblock and handled using anexceptblock.
Default Behavior¶
- If the raised exception is not handled, Python stops the program immediately and displays an error message.
- This abrupt termination can be avoided by using exception handling mechanisms.
Key Components of Exception Handling¶
| Keyword | Purpose |
|---|---|
try |
Defines a block of code where exceptions may occur. |
except |
Defines what to do when an exception is raised in the try block. |
else |
Runs only if no exceptions are raised in the try block. |
finally |
Executes regardless of whether an exception occurred or not. |
Handling Exceptions with try and except Block¶
- Python provides the
trystatement to catch and handle exceptions gracefully. - Within a
tryblock, you place code that might potentially fail. - If an error is detected during execution, Python checks for a matching
exceptblock to handle it.
Generic Exception Handling¶
Python allows you to create an except block without specifying a particular exception type. This is known as generic exception handling.
This block will catch any exception, making it useful as a fallback for unexpected errors.
Note: Overusing generic
except: clauses is discouraged, as it hides the exact cause of the problem.
Syntax
try:
# Code that may raise an exception
except:
# Code to handle the error
else:
# Code that runs if no exception occurs
finally:
# Code that always executes (cleanup)
Example:
try:
# Code that might raise an exception
risky_operation = 10 / 0
except:
# This will catch any type of exception
print("An unexpected error occurred!")
An unexpected error occurred!
Example with else and finally:
try:
# Code that might raise an exception
risky_operation = 10 / 2
except:
# This will catch any type of exception
print("An unexpected error occurred!")
else:
# Executes only if no exception occurs
print("Operation completed successfully.")
finally:
# Code that always executes
print("Execution completed.")
Operation completed successfully. Execution completed.
Catching Specific Exceptions¶
Using a generic exception handler like
except Exception:orexcept:might seem convenient, but it’s bad practice because:- It hides unexpected bugs.
- It makes debugging difficult.
- It may catch system-level exceptions unintentionally.
Instead of catching all exceptions, it is a good practice to handle specific exception types.
This approach makes your code more readable, debuggable, and robust.
For instance, if you’re working with file operations, catch only file-related errors like FileNotFoundError or IOError.
By specifying which exceptions to catch, you can customize how your program responds to each type of error.
Syntax
try:
# Code that might cause an exception
except ExceptionType:
# Code to handle the error
else:
# Code to execute if no exception occurs
finally:
# Code that always executes (cleanup)
Example:
try:
# Code that might raise an exception
risky_operation = 10 / 0
except ZeroDivisionError:
# Handle division by zero
print("Error: Cannot divide by zero.")
Error: Cannot divide by zero.
Example with else and finally:
try:
# Code that might raise an exception
risky_operation = 10 / 0
except ZeroDivisionError:
# Handle division by zero
print("Error: Cannot divide by zero.")
else:
# Executes only if no exception occurs
print("Operation completed successfully.")
finally:
# Code that always executes
print("Execution completed.")
Error: Cannot divide by zero. Execution completed.
Handling Multiple Exceptions¶
A single try block can be followed by multiple except clauses, each tailored to handle a specific type of exception.
This is helpful when the code in the try block can raise different types of exceptions.
def divide_numbers(a, b):
try:
# Try dividing two numbers
result = a / b
print("Result:", result)
except ZeroDivisionError:
# Handle division by zero
print("Error: Cannot divide by zero.")
except TypeError:
# Handle invalid data types
print("Error: Both inputs must be numbers.")
except Exception:
# Catch-all for any other unforeseen exceptions
print("Unexpected Error:")
# Example calls
divide_numbers(10, 2) # Expected output: Result: 5.0
divide_numbers(10, 0) # Expected output: Error: Cannot divide by zero.
divide_numbers(10, 'a') # Expected output: Error: Both inputs must be numbers.
Result: 5.0 Error: Cannot divide by zero. Error: Both inputs must be numbers.
Explanation:
- ZeroDivisionError handles the case when b is 0.
- TypeError handles invalid input types (like dividing by a string).
- The generic Exception block handles any other unforeseen exceptions.
Catching Multiple Specific Exceptions Together¶
- You can handle multiple exception types in a single block by grouping them in parentheses:
try:
# Some risky operation
except (TypeError, ValueError):
print("Caught either a TypeError or ValueError")
try:
num = int(input("Enter a number: "))
print(10 / num)
except (ValueError, ZeroDivisionError) as e:
print("Input error: ", e)
Enter a number: 0 Input error: division by zero
How it works:
- If the user enters a non-numeric value, a ValueError occurs.
- If the user enters 0, a ZeroDivisionError occurs.
- Both are caught by the same handler.
Using else Clause¶
- An optional else block can be included after all except blocks.
- The else block runs only when no exceptions are raised in the try block.
- This is a good place for code that should only execute when the try block succeeds completely.
try:
print("Hello from Intensity Coding!")
except:
print("An error occurred")
else:
print("No exceptions occurred")
# Output:
# Hello from Intensity Coding!
# No exceptions occurred
Hello from Intensity Coding! No exceptions occurred
Using finally Block¶
The
finallyblock is always executed—no matter what happens in thetryorexceptblocks.It is commonly used for resource clean-up, such as closing files or releasing memory.
try:
print(z)
except:
print("An error occurred")
finally:
print("This block always runs")
# Output:
# An error occurred
# This block always runs
An error occurred This block always runs
Capturing Exception Arguments¶
When handling exceptions in Python, it's often useful to access specific details about the error. This is done by capturing the exception object using the
askeyword in theexceptclause.By doing so, you gain access to the underlying message or cause of the exception, which helps in debugging and logging.
Single Exception Handling with Argument¶
Syntax
try:
# Code that may raise an error
except ExceptionType as error_variable:
# Use 'error_variable' to access the error details
# Function to convert a value to an integer
def convert_to_integer(value):
try:
return int(value)
except ValueError as error_info:
print("Conversion failed. Reason:", error_info)
# Test the function with invalid input
convert_to_integer("intensity")
# Output:
# Conversion failed. Reason: invalid literal for int() with base 10: 'intensity'
Conversion failed. Reason: invalid literal for int() with base 10: 'intensity'
Handling Multiple Exceptions with a Shared Variable¶
try:
# Code that may raise different types of exceptions
except (TypeError, ValueError) as error_variable:
# 'error' captures whichever exception was raised
ExceptionType: The specific type of error you want to catch (e.g., ValueError, TypeError, etc.)error_variable: A variable that stores the exception object.This object contains:
- The type of error (name of the exception)
- The program’s state at the moment the error occurred
- A descriptive message explaining the cause of the error
try:
num = int(input("Enter a number: "))
print(10 / num)
except (ValueError, ZeroDivisionError) as e:
print("Input error: ", e)
Enter a number: 0 Input error: division by zero
- In this example:
- The
evariable captures the actual error message raised from either ZeroDivisionError or ValueError. - This makes it easier to handle and log the root cause of issues without writing separate blocks for each.
- The
Nested try Blocks¶
- In Python, nested try blocks allow you to handle exceptions at multiple levels. A nested try block is simply a try block placed inside another try block. This structure is useful for separating error handling for different parts of your code, making it more robust and easier to debug.
def divide_numbers(a,b):
"""
Function to divide two numbers provided by the user.
Handles invalid input and division by zero using nested try blocks.
"""
try:
# Outer try block: Handle input errors
num1 = int(a)
num2 = int(b)
try:
# Inner try block: Handle division errors
result = num1 / num2
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
else:
print("Division result is:", result)
finally:
print("Inner try block executed.")
except ValueError:
print("Error: Invalid input. Please enter integers only.")
finally:
print("Outer try block executed.")
# Call the function
divide_numbers(10,2)
print("----------")
divide_numbers(10,0)
print("----------")
divide_numbers(10,'a')
Division result is: 5.0 Inner try block executed. Outer try block executed. ---------- Error: Cannot divide by zero. Inner try block executed. Outer try block executed. ---------- Error: Invalid input. Please enter integers only. Outer try block executed.
Raising Exceptions Manually¶
While Python provides built-in exceptions (like ValueError, TypeError, etc.), you can raise exceptions manually to handle situations where you want the program to explicitly signal an error.
The
raisekeyword allows you to trigger an exception yourself:
raise ExceptionType("Error message")
Example 1: Raise a general exception¶
x = -5
if x < 0:
raise Exception("Negative numbers are not allowed in Intensity Coding!")
# Output:
# Exception: Negative numbers are not allowed in Intensity Coding!
--------------------------------------------------------------------------- Exception Traceback (most recent call last) /tmp/ipython-input-5-3440258374.py in <cell line: 0>() 1 x = -5 2 if x < 0: ----> 3 raise Exception("Negative numbers are not allowed in Intensity Coding!") 4 5 # Output: Exception: Negative numbers are not allowed in Intensity Coding!
Example 2: Raise a specific exception¶
# Function to check age input
def check_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
else:
return f"Age is {age}"
# Testing
print(check_age(25)) # Works fine
print(check_age(-5)) # Raises ValueError: Age cannot be negative
Age is 25
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) /tmp/ipython-input-117348710.py in <cell line: 0>() 8 # Testing 9 print(check_age(25)) # Works fine ---> 10 print(check_age(-5)) # Raises ValueError: Age cannot be negative /tmp/ipython-input-117348710.py in check_age(age) 2 def check_age(age): 3 if age < 0: ----> 4 raise ValueError("Age cannot be negative") 5 else: 6 return f"Age is {age}" ValueError: Age cannot be negative
Exception Chaining¶
- When handling errors in Python, sometimes one exception leads to another. For example, you might catch one error, handle it partially, but then raise a new one because the situation still can’t be resolved.
- In such cases, Python allows you to chain exceptions — linking the original cause (the first exception) with the new one you raise later.
- This concept is known as Exception Chaining.
Why Do We Need Exception Chaining?¶
- Imagine this sequence:
- You attempt an operation (like opening a file).
- That operation fails (e.g., FileNotFoundError).
- You catch that exception and raise another one (e.g., RuntimeError) to give a higher-level message.
Without chaining, Python would forget the original cause of the error. With chaining, you preserve both:
- The original exception (cause)
- The new exception (effect)
This helps in debugging, as you see both what failed originally and why a new exception was raised afterward.
Syntax of Exception Chaining¶
- Python supports two forms of chaining:
1. Implicit Exception Chaining¶
- Occurs automatically when you raise a new exception inside an except block.
try:
1 / 0
except ZeroDivisionError:
raise ValueError("Invalid mathematical operation")
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) /tmp/ipython-input-2729777627.py in <cell line: 0>() 1 try: ----> 2 1 / 0 3 except ZeroDivisionError: ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: ValueError Traceback (most recent call last) /tmp/ipython-input-2729777627.py in <cell line: 0>() 2 1 / 0 3 except ZeroDivisionError: ----> 4 raise ValueError("Invalid mathematical operation") ValueError: Invalid mathematical operation
2. Explicit Exception Chaining with from¶
- You can explicitly specify the cause of a new exception using the
fromkeyword.
try:
num = int("abc") # will raise ValueError
except ValueError as e:
raise TypeError("Data type conversion failed") from e
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) /tmp/ipython-input-3085465140.py in <cell line: 0>() 1 try: ----> 2 num = int("abc") # will raise ValueError 3 except ValueError as e: ValueError: invalid literal for int() with base 10: 'abc' The above exception was the direct cause of the following exception: TypeError Traceback (most recent call last) /tmp/ipython-input-3085465140.py in <cell line: 0>() 2 num = int("abc") # will raise ValueError 3 except ValueError as e: ----> 4 raise TypeError("Data type conversion failed") from e TypeError: Data type conversion failed
Suppressing the Context¶
- Sometimes you don’t want to show the original cause.
- You can suppress the automatic chaining by raising with from None.
try:
1 / 0
except ZeroDivisionError:
raise RuntimeError("Computation failed") from None
--------------------------------------------------------------------------- RuntimeError Traceback (most recent call last) /tmp/ipython-input-406400982.py in <cell line: 0>() 2 1 / 0 3 except ZeroDivisionError: ----> 4 raise RuntimeError("Computation failed") from None RuntimeError: Computation failed
Assert Statement¶
An assert statement is a built-in debugging tool used to verify that a condition holds true while a program is running.
When the condition evaluates to True, the program continues normally. If the condition evaluates to False, Python raises an AssertionError, optionally showing a custom message.
Assertions are used for detecting logical mistakes early, preventing invalid data from flowing through the program and ensuring preconditions and postconditions of algorithms.
Syntax of an Assert Statement¶
Basic Form
assert condition
With a Custom Error Message
assert condition, "Error message"
Example: Validating Input Range¶
# Example: Ensure a value lies within an expected range
x = 12
# Assert that x is less than 10
assert x < 10, "Value of x exceeded the allowed threshold"
# Expected: AssertionError (since 12 < 10 is False)
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) /tmp/ipython-input-589683157.py in <cell line: 0>() 3 4 # Assert that x is less than 10 ----> 5 assert x < 10, "Value of x exceeded the allowed threshold" 6 7 # Expected: AssertionError (since 12 < 10 is False) AssertionError: Value of x exceeded the allowed threshold
When Not to Use Assertions¶
- Assertions should not be used for user-facing error handling. They are mainly for internal checks during development.
The Exception Lifecycle¶
- The lifecycle of an exception describes the journey from when the error occurs to when it is handled (or causes the program to terminate).
- It typically includes five stages:
Stage 1: Exception Occurrence¶
When Python executes a line that leads to an error, an exception object is created automatically.
The type of exception depends on the nature of the error (e.g., ZeroDivisionError, ValueError, FileNotFoundError).
- At this point, Python pauses execution and prepares to handle the error.
# Stage 1: Exception occurrence
result = 10 / 0 # Raises ZeroDivisionError
Stage 2: Exception Propagation¶
- If the exception is not handled where it occurs, Python propagates it up the call stack.
- This means it checks if any calling function or block has a matching except handler.
def divide(a, b):
return a / b # Exception occurs here
def main():
divide(10, 0) # Propagation to main()
main() # If not handled, it will propagate to interpreter
- If no handler is found at any level, the exception continues to bubble up to the top level (Python interpreter).
Stage 3: Exception Handling¶
- If Python finds a matching except block for the raised exception, it executes that block.
- Once handled, program control resumes after the except block.
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Handled Exception: {e}")
print("Program continues...")
Handled Exception: division by zero Program continues...
Stage 4: Exception Termination (Uncaught Exception)¶
If the exception is not handled anywhere in the call stack, Python terminates the program.
Before termination, Python prints the exception traceback, showing where the error occurred.
def divide(a, b):
return a / b # Unhandled
divide(10, 0)
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) /tmp/ipython-input-3535963310.py in <cell line: 0>() 2 return a / b # Unhandled 3 ----> 4 divide(10, 0) /tmp/ipython-input-3535963310.py in divide(a, b) 1 def divide(a, b): ----> 2 return a / b # Unhandled 3 4 divide(10, 0) ZeroDivisionError: division by zero
Stage 5: Cleanup Phase (Finally Block)¶
The finally block executes no matter what — whether the exception occurs or not.
It is used for releasing resources like files, database connections, etc.
# Example
try:
file = open("data.txt", "r")
content = file.read()
except FileNotFoundError:
print("File not found!")
finally:
print("Closing resources...")
File not found! Closing resources...
Advantages of Exception Handling in Python¶
- Improves Program Stability: Prevents unexpected crashes by catching errors and allowing controlled recovery.
- Clearer Code Structure: Keeps normal logic separate from error-handling logic, reducing clutter.
- Easier Debugging: Python tracebacks highlight where an exception occurred, speeding up diagnosis.
- Better Resource Control: Ensures files, database connections, and network links are closed correctly.
- Cleaner Flow Control: Reduces the need for multiple conditional checks by handling errors directly.
Disadvantages of Exception Handling in Python¶
- Possible Overuse: Using exceptions when simple checks would suffice can complicate code.
- Performance Cost: Raising and catching exceptions is slower than basic condition checks.
- Hidden Bugs: Broad exception blocks may suppress real programming errors.
- Higher Complexity: Managing many exception cases can make code harder to read.
- Security Risks: Poorly managed exceptions may expose sensitive details through error messages.
Categories of Exceptions¶
Python broadly groups exceptions into two categories:
- User-Defined Exceptions
- Built-in Exceptions
Both types ultimately inherit from Python’s root exception class:
BaseException
User-Defined Exceptions¶
In addition to using built-in exception types like
ValueErrororTypeError, Python allows you to define custom exception classes by extending existing exceptions such as Exception or RuntimeError.This approach is especially useful when:
- You want to handle application-specific errors in a clean and descriptive way.
- You need to differentiate between different types of errors within your domain.
- You wish to add context or custom attributes to the error.
A custom exception is created by defining a new class that derives from the built-in Exception class.
1. Basic Custom Exception¶
- The simplest possible custom exception includes only a class definition:
# Minimal custom exception
class CustomException(Exception):
pass
- This class doesn’t add new behavior yet, but it allows you to raise and catch a clearly named exception.
Raising the Exception
raise CustomException("This is a custom exception")
# Expected Output:
# CustomException: This is a custom exception
--------------------------------------------------------------------------- CustomException Traceback (most recent call last) /tmp/ipython-input-879847413.py in <cell line: 0>() ----> 1 raise CustomException("This is a custom exception") 2 3 # Expected Output: 4 # CustomException: This is a custom exception CustomException: This is a custom exception
2. Adding Functionality to Custom Exceptions¶
In many applications, you need the exception to store additional details such as:
- An error code
- Metadata about the failure
- A specific value related to the issue
To do this, override the constructor and store extra attributes
# Custom exception with additional attributes
class CustomException(Exception):
def __init__(self, message, error_code):
# Pass the message to the base Exception class
super().__init__(message)
# Store metadata for programmatic handling
self.error_code = error_code
- This makes the exception more informative and useful during debugging or error logging.
Raising the Enhanced Exception
raise CustomException("This is a custom exception", 404)
# Expected Output:
# CustomException: This is a custom exception
--------------------------------------------------------------------------- CustomException Traceback (most recent call last) /tmp/ipython-input-3853781119.py in <cell line: 0>() ----> 1 raise CustomException("This is a custom exception", 404) 2 3 # Expected Output: 4 # CustomException: This is a custom exception CustomException: This is a custom exception
Accessing Extra Attributes During Handling
try:
raise CustomException("Training failed due to invalid input", 501)
except CustomException as err:
print("Message:", err)
print("Error Code:", err.error_code)
# Expected Output:
# Message: Training failed due to invalid input
# Error Code: 501
Message: Training failed due to invalid input Error Code: 501
Python Built-in Exceptions¶
- Python includes a comprehensive set of built-in exception classes designed to capture common runtime errors.
- These exceptions cover mathematical issues, lookup failures, file operations, system signals, and much more.
- All built-in exceptions ultimately inherit from the root class
BaseException. - This forms the foundation of Python’s exception model and ensures consistent behavior across all error types.
Exception hierarchy(Built-in)¶
- Below is the structured hierarchy of Python’s main built-in exceptions. This layout shows how broader exception types group together specific error categories. Taken from the official Python documentation
BaseException
├── BaseExceptionGroup
├── GeneratorExit
├── KeyboardInterrupt
├── SystemExit
└── Exception
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── AssertionError
├── AttributeError
├── BufferError
├── EOFError
├── ExceptionGroup [BaseExceptionGroup]
├── ImportError
│ └── ModuleNotFoundError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── MemoryError
├── NameError
│ └── UnboundLocalError
├── OSError
│ ├── BlockingIOError
│ ├── ChildProcessError
│ ├── ConnectionError
│ │ ├── BrokenPipeError
│ │ ├── ConnectionAbortedError
│ │ ├── ConnectionRefusedError
│ │ └── ConnectionResetError
│ ├── FileExistsError
│ ├── FileNotFoundError
│ ├── InterruptedError
│ ├── IsADirectoryError
│ ├── NotADirectoryError
│ ├── PermissionError
│ ├── ProcessLookupError
│ └── TimeoutError
├── ReferenceError
├── RuntimeError
│ ├── NotImplementedError
│ ├── PythonFinalizationError
│ └── RecursionError
├── StopAsyncIteration
├── StopIteration
├── SyntaxError
│ └── IndentationError
│ └── TabError
├── SystemError
├── TypeError
├── ValueError
│ └── UnicodeError
│ ├── UnicodeDecodeError
│ ├── UnicodeEncodeError
│ └── UnicodeTranslateError
└── Warning
├── BytesWarning
├── DeprecationWarning
├── EncodingWarning
├── FutureWarning
├── ImportWarning
├── PendingDeprecationWarning
├── ResourceWarning
├── RuntimeWarning
├── SyntaxWarning
├── UnicodeWarning
└── UserWarning
Respecting Exception Hierarchy¶
When working with multiple except blocks, it is important to understand how Python decides which handler to execute.
Python checks each except clause in the order they appear, matching the first compatible exception type.
Because of this, the order of exception classes must follow the exception hierarchy correctly.
A general rule is:
- Always place more specific exceptions first.
- Place broader or base exceptions later.
- Avoid catching the base Exception class unless there is a good reason, because it hides errors that should be handled explicitly.
Why Exception Order Matters¶
- Python's exception mechanism stops the search once it finds the first matching handler. Since classes like ValueError, TypeError, and others inherit from the base Exception class, placing Exception too early will prevent more specific handlers from ever running.
Correct Ordering of Multiple Exceptions¶
# Example : Proper exception ordering
try:
# Code block that may cause various errors
value = int("abc") # Will raise ValueError
except ValueError:
# Handles conversion errors
print("Invalid conversion: a ValueError occurred.")
except Exception:
# Fallback for unexpected exception types
print("An unexpected exception occurred.")
# Expected Output:
# Invalid conversion: a ValueError occurred.
Invalid conversion: a ValueError occurred.
- Here, ValueError is handled first. Any other exception type still derived from Exception will fall into the next block.
What Happens if the Base Exception Comes First?¶
- If a broad exception class such as Exception is placed before specific handlers, Python will never reach those specific handlers. This leads to unreachable code.
# Example : Incorrect ordering (should be avoided)
try:
x = 10 / 0 # Raises ZeroDivisionError
except Exception as e:
print(f"General handler: {e}") # This executes first
except ZeroDivisionError:
print("You will never see this message.")
except ValueError:
print("This is also unreachable.")
# Expected Output:
# General handler: division by zero
General handler: division by zero
- Because Exception catches everything, the two specific handlers below it will never execute.
Understand Built-in Exceptions¶
| No. | Exception Name | Description |
|---|---|---|
| 1 | BaseException |
Root class for all built-in exceptions and used for catching all exceptions in generic scenarios. |
| 2 | BaseExceptionGroup |
Represents a group of exceptions raised together (Python 3.11+). |
| 3 | GeneratorExit |
Raised automatically when a generator’s close() method is called. |
| 4 | KeyboardInterrupt |
Triggered when the user interrupts execution manually (e.g., Ctrl+C, Ctrl+Z). |
| 5 | SystemExit |
Raised when the sys.exit() function is called to stop the interpreter. |
| 6 | Exception |
Base class for most common runtime exceptions. |
| 7 | ArithmeticError |
Base class for numeric and arithmetic issues. Indicates issues in arithmetic or numeric operations (e.g., overflow, division errors). |
| 8 | FloatingPointError |
Floating-point computation error (rare). |
| 9 | OverflowError |
Occurs when a numeric operation exceeds the limits of the number type. |
| 10 | ZeroDivisionError |
Raised when dividing by zero using / or //. |
| 11 | AssertionError |
Triggered when an assert statement condition evaluates to false. |
| 12 | AttributeError |
Raised when an invalid or missing attribute is accessed on an object. |
| 13 | BufferError |
Issues related to buffer operations. |
| 14 | EOFError |
Occurs when input() reaches an unexpected end-of-file (EOF) without reading data. |
| 15 | ExceptionGroup |
Groups multiple Exception objects (Python 3.11+). |
| 16 | ImportError |
Indicates failure when trying to import a module that doesn't exist or fails to load. |
| 17 | ModuleNotFoundError |
Specific case when a module cannot be located. |
| 18 | LookupError |
The base class for lookup-related errors like IndexError and KeyError. |
| 19 | IndexError |
Occurs when trying to access a sequence (like list or string) using an invalid index. |
| 20 | KeyError |
Raised when a non-existent key is accessed in a dictionary. |
| 21 | MemoryError |
Raised when Python runs out of memory during execution. |
| 22 | NameError |
Indicates that a variable or identifier is not defined. |
| 23 | UnboundLocalError |
Raised when a local variable is accessed before it has been assigned a value. |
| 24 | OSError |
Base class for OS related failures. |
| 25 | BlockingIOError |
Raised when non-blocking operation would block. |
| 26 | ChildProcessError |
Failure related to child process operations occurs. |
| 27 | ConnectionError |
Base class for network/connection failures. |
| 28 | BrokenPipeError |
Raised when writing to closed pipe/socket. |
| 29 | ConnectionAbortedError |
Raised when connection aborted by the peer. |
| 30 | ConnectionRefusedError |
Raised when connection attempt refused by server. |
| 31 | ConnectionResetError |
Raised when connection reset by peer. |
| 32 | FileExistsError |
Raised when attempt to create file/dir that already exists. |
| 33 | FileNotFoundError |
Raised when file or directory does not exist. |
| 34 | InterruptedError |
Raised when system call interrupted by a signal. |
| 35 | IsADirectoryError |
Raised when expected a file but found a directory. |
| 36 | NotADirectoryError |
Raised when path component expected to be directory but was not. |
| 37 | PermissionError |
Raised when insufficient permissions for operation. |
| 38 | ProcessLookupError |
Raised when process does not exist. |
| 39 | TimeoutError |
Raised when operation times out. |
| 40 | ReferenceError |
Raised when weak reference refers to a deleted object. |
| 41 | RuntimeError |
General runtime error not fitting other categories. |
| 42 | NotImplementedError |
Occurs when an abstract method requires an inherited class to override the method |
| 43 | PythonFinalizationError |
Error occurring during interpreter shutdown. |
| 44 | RecursionError |
Raised when maximum recursion depth exceeded. |
| 45 | StopAsyncIteration |
Raised when end of asynchronous iterator. |
| 46 | StopIteration |
Raised by next() when there are no more items in an iterator. |
| 47 | SyntaxError |
Raised when the Python interpreter detects incorrect syntax. |
| 48 | IndentationError |
Raised due to incorrect or inconsistent indentation in the code. |
| 49 | TabError |
Occurs when indentation consists of tabs or spaces |
| 50 | SystemError |
Raised when the interpreter encounters an system error (not user-related). |
| 51 | TypeError |
Raised when you try to use two values of different or unsupported data types together. |
| 52 | ValueError |
Occurs when there is a incorrect value in a specified data type |
| 53 | UnicodeError |
Base for Unicode encoding/decoding issues. |
| 54 | UnicodeDecodeError |
Raised when decoding a byte stream into Unicode fails. |
| 55 | UnicodeEncodeError |
Raised during errors in converting Unicode to a specific encoding format. |
| 56 | UnicodeTranslateError |
Occurs when a Unicode translation process fails. |
Some Example of Built-in Exceptions¶
BaseException¶
- The root of all exceptions in Python. Rarely used directly; usually used for catching all exceptions in generic scenarios.
try:
raise BaseException("This is a base exception")
except BaseException as e:
print(f"Caught exception: {e}")
# Output: Caught exception: This is a base exception
Caught exception: This is a base exception
KeyboardInterrupt¶
A KeyboardInterrupt is raised when a running Python program is manually stopped by the user.
This usually happens when someone presses Ctrl + C in the terminal, which sends an interrupt signal telling the program to halt immediately.
This exception commonly appears in two situations:
- When a program is stuck in a long-running or infinite loop.
- When Python is waiting for user input through input() or similar operations.
Example 1: Interrupting an Infinite Loop
# Handling KeyboardInterrupt in an Infinite Loop
try:
while True:
pass
except KeyboardInterrupt:
print("Program interrupted by the user.")
Program interrupted by the user.
- When the user presses Ctrl + C, Python terminates the loop and triggers the KeyboardInterrupt exception, which is handled by the except block.
Example 2: Interrupting During User Input
- KeyboardInterrupt can also be triggered when Python is waiting for input.
# Handling KeyboardInterrupt During Input
try:
name = input("Please enter your name: ")
print(f"Hello, {name}! Welcome to Intensity Coding.")
except KeyboardInterrupt:
print("Input cancelled. Exiting program safely.")
Input cancelled. Exiting program safely.
What Happens Here?
- Python waits at the input() line.
- If the user presses Ctrl + C instead of typing a name, a KeyboardInterrupt is raised.
- The except block catches the interruption and prints a clean exit message.
Exception¶
- Most exceptions inherit from this class. Exception serves as the base class for nearly all common errors.
- When you catch Exception, you are essentially handling a wide range of runtime issues such as type errors, value errors, indexing mistakes, and more.
- It is often used when you want to catch any non-fatal exception without specifying a particular error type
# Demonstrating the Base Exception Class
try:
# Attempting an invalid numeric conversion triggers a ValueError,
# but here we catch it using the general Exception class.
result = int("not_a_number")
except Exception as err:
print(f"General Exception Caught: {err}")
# Expected Output:
# General Exception Caught: invalid literal for int() with base 10: 'not_a_number'
General Exception Caught: invalid literal for int() with base 10: 'not_a_number'
Explanation
- Converting a non-numeric string to an integer raises a ValueError.
- Because ValueError is a subclass of Exception, the generic except block handles it.
- This is helpful when you want broad protection but should be used carefully to avoid masking specific errors.
OverflowError¶
- Raised when a numeric calculation exceeds the maximum limit that Python can handle.
- Commonly occurs with floating-point operations or very large exponentials.
- Mostly seen in math functions like math.exp() for huge numbers.
import math
try:
# Attempt to calculate exponential of a very large number
result = math.exp(1000) # Exceeds floating-point limits
except OverflowError as e:
print("Caught OverflowError:", e)
Caught OverflowError: math range error
ZeroDivisionError¶
- A ZeroDivisionError occurs when a number is divided by zero. In mathematics, division by zero is undefined.
- Python enforces this rule and raises an exception instead of producing an unpredictable output.
# ZeroDivisionError Demonstration
try:
value = 12 / 0
except ZeroDivisionError as err:
print(f"Caught ZeroDivisionError: {err}")
Caught ZeroDivisionError: division by zero
AssertionError¶
An AssertionError occurs when an assert statement evaluates to False.
Assertions act as internal checkpoints that verify assumptions made while writing code.
They are useful for:
- Validating intermediate results
- Catching logical mistakes early
- Ensuring conditions remain true during execution
- Making debugging easier
Assertions should not be used for handling user-facing errors. They are primarily for developers to verify correct program behavior.
Syntex
assert <condition>, <optional message>
x = 3
try:
assert x > 10, "x must be greater than 10"
except AssertionError as e:
print(e)
# Output: x must be greater than 10
x must be greater than 10
AttributeError¶
- Raised when an object does not have the attribute or method you are trying to access.
- Commonly occurs when calling a non-existent method or accessing a wrong property of an object.
try:
my_list = [1, 2, 3]
# Attempt to call a non-existent method
my_list.push(4) # Lists have append(), not push()
except AttributeError as e:
print("Caught AttributeError:", e)
Caught AttributeError: 'list' object has no attribute 'push'
ImportError¶
- Raised when importing a module or object fails, often because the module exists but the specific object doesn’t.
try:
# Attempt to import a function that does not exist in math
from math import unknown_function
except ImportError as e:
print("Caught ImportError:", e)
Caught ImportError: cannot import name 'unknown_function' from 'math' (unknown location)
ModuleNotFoundError¶
- Raised when trying to import a module that does not exist.
try:
# Attempt to import a module that is not installed
import some_nonexistent_module
except ModuleNotFoundError as e:
print("Caught ModuleNotFoundError:", e)
Caught ModuleNotFoundError: No module named 'some_nonexistent_module'
IndexError¶
- Raised when trying to access an element using an invalid index in a sequence (like a list, tuple, or string).
try:
my_list = [10, 20, 30]
print(my_list[5]) # Index 5 does not exist
except IndexError as e:
print("Caught IndexError:", e)
Caught IndexError: list index out of range
KeyError¶
- Raised when trying to access a dictionary key that does not exist.
try:
my_dict = {"a": 1, "b": 2}
print(my_dict["c"]) # Key 'c' does not exist
except KeyError as e:
print("Caught KeyError:", e)
Caught KeyError: 'c'
NameError¶
- Raised when a variable or function name is not defined in the current scope.
try:
# Attempting to use a variable that does not exist
print(my_variable)
except NameError as e:
print("Caught NameError:", e)
Caught NameError: name 'my_variable' is not defined
UnboundLocalError¶
- Raised when a local variable is accessed before it has been assigned a value.
try:
def calculate():
# Trying to use x before assigning
print(x)
x = 10
calculate()
except UnboundLocalError as e:
print("Caught UnboundLocalError:", e)
Caught UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
FileExistsError¶
- Raised when an operation tries to create a file or directory that already exists.
import os
try:
# Attempt to create a directory that already exists
os.mkdir("my_folder") # First time: creates successfully
os.mkdir("my_folder") # Second time: raises FileExistsError
except FileExistsError as e:
print("Caught FileExistsError:", e)
Caught FileExistsError: [Errno 17] File exists: 'my_folder'
FileNotFoundError¶
- Raised when an operation tries to access a file or directory that does not exist
try:
# Attempt to open a file that does not exist
with open("nonexistent_file.txt", "r") as f:
content = f.read()
except FileNotFoundError as e:
print("Caught FileNotFoundError:", e)
Caught FileNotFoundError: [Errno 2] No such file or directory: 'nonexistent_file.txt'
IsADirectoryError¶
- Raised when a file operation expects a file, but the path points to a directory instead.
- Commonly occurs when trying to open a directory as a file.
try:
# Suppose "my_folder" is an existing directory
with open("my_folder", "r") as f: # Attempt to open a directory as a file
content = f.read()
except IsADirectoryError as e:
print("Caught IsADirectoryError:", e)
Caught IsADirectoryError: [Errno 21] Is a directory: 'my_folder'
NotADirectoryError¶
- Raised when a path operation expects a directory, but the target is not a directory.
try:
# Suppose "file.txt" is a regular file, not a directory
with open("file.txt", "w") as f:
f.write("Hello, Intensity Coding!")
# Attempt to list contents of a file as if it were a directory
import os
os.listdir("file.txt") # Raises NotADirectoryError
except NotADirectoryError as e:
print("Caught NotADirectoryError:", e)
Caught NotADirectoryError: [Errno 20] Not a directory: 'file.txt'
RecursionError¶
- Raised when the maximum recursion depth is exceeded.
- Happens if a recursive function keeps calling itself without a base condition.
try:
# Recursive function without a base condition
def infinite_recursion():
return infinite_recursion() + 1
infinite_recursion() # This will exceed recursion depth
except RecursionError as e:
print("Caught RecursionError:", e)
Caught RecursionError: maximum recursion depth exceeded
StopIteration¶
- A StopIteration exception is raised when an iterator has no more items to return.
- Happens commonly with manual iteration using next(). Python automatically handles it in for loops, but when using next() directly, it must be handled.
# Create a simple iterator
numbers = iter([10, 20, 30])
try:
print(next(numbers)) # 10
print(next(numbers)) # 20
print(next(numbers)) # 30
print(next(numbers)) # No more items, raises StopIteration
except StopIteration as e:
print("Caught StopIteration: No more elements in the iterator")
10 20 30 Caught StopIteration: No more elements in the iterator
SyntaxError¶
- A SyntaxError occurs when Python encounters invalid syntax.
- For example, forgetting a colon in a function definition.
# Incorrect syntax: missing colon after function definition
def greet()
print("Hello, Intensity Coding!")
File "/tmp/ipython-input-2486299230.py", line 2 def greet() ^ SyntaxError: expected ':'
IndentationError¶
- An IndentationError happens when the code blocks are not properly indented according to Python rules
# Incorrect indentation: the print statement is not indented properly
if 7 > 2:
print("Seven is greater than two!")
File "/tmp/ipython-input-3383252422.py", line 3 print("Seven is greater than two!") ^ IndentationError: expected an indented block after 'if' statement on line 2
TypeError¶
- A TypeError occurs when an operation is applied to data of an incompatible type.
x = "5" # string
y = 3 # integer
# Attempting an invalid operation
try:
result = x + y # Python does not know how to add str + int
except TypeError as e:
print("TypeError occurred:", e)
# Expected Output:
# TypeError occurred: can only concatenate str (not "int") to str
TypeError occurred: can only concatenate str (not "int") to str
ValueError¶
- Occurs when there is a incorrect value in a specified data type
value = "abc"
try:
num = int(value) # Python cannot convert alphabetic characters to int
except ValueError as e:
print("ValueError occurred:", e)
# Expected Output:
# ValueError occurred: invalid literal for int() with base 10: 'abc'
ValueError occurred: invalid literal for int() with base 10: 'abc'
UnicodeDecodeError¶
- Raised when Python tries to decode bytes into a string but the given byte sequence is invalid for the specified encoding.
- Common when reading files with the wrong encoding.
# Example: Wrong decoding attempt
data = b"\xe2\x28\xa1" # Invalid UTF-8 sequence
try:
text = data.decode("utf-8")
except UnicodeDecodeError as e:
print("UnicodeDecodeError occurred:", e)
# Expected Output:
# UnicodeDecodeError occurred: 'utf-8' codec can't decode byte 0xe2 in position 0: invalid continuation byte
UnicodeDecodeError occurred: 'utf-8' codec can't decode byte 0xe2 in position 0: invalid continuation byte
UnicodeEncodeError¶
- Raised when Python tries to encode a Unicode string into bytes, but the target encoding cannot represent some characters.
- Frequently appears while writing text containing emojis or non-ASCII characters to ASCII-only outputs.
# Encoding a string with characters ASCII cannot handle
text = "Intensity Coding – Python ✨"
try:
encoded = text.encode("ascii")
except UnicodeEncodeError as e:
print("UnicodeEncodeError occurred:", e)
# Expected Output:
# UnicodeEncodeError occurred: 'ascii' codec can't encode character '\u2013' in position 17: ordinal not in range(128)
UnicodeEncodeError occurred: 'ascii' codec can't encode character '\u2013' in position 17: ordinal not in range(128)
BaseExceptionGroup¶
- Represents a group of exceptions raised together (Python 3.11+).
- This feature is especially useful in concurrent or asynchronous tasks where multiple failures can occur in parallel.
- Traditional exception handling works well when a program raises one error at a time, but breaks down when multiple independent tasks fail concurrently.
- BaseExceptionGroup provides a structured way to bundle many exceptions together and raise them as a single object.
- The idea is simple: Instead of raising a single exception, you can raise a collection of exceptions wrapped inside an exception group.
Why Do We Need Exception Groups?
- Consider asynchronous execution:
- Several tasks run concurrently.
- Multiple tasks fail at the same time.
- Traditional exception handling captures only one exception.
- Exception groups solve this puzzle by allowing the program to raise, propagate, and handle several exceptions in a structured way.
BaseExceptionGroup vs. ExceptionGroup
| Class | Purpose |
|---|---|
BaseExceptionGroup |
Low-level base class for groups of exceptions |
ExceptionGroup |
Derived class used for normal exceptions |
ExceptionGroupbehaves like a normal exception but holds multiple sub-exceptions.
Structure of Exception Groups
- Basic Form :
BaseExceptionGroup(message, exceptions) - Key Attributes :
- message: A description of the group.
- exceptions: A list of contained exceptions (or nested groups).
- Example :
ExceptionGroup(
"Group message",
[
ValueError("invalid data"),
TypeError("wrong type"),
ExceptionGroup(
"Nested group",
[
RuntimeError("inner failure"),
KeyError("missing field")
]
)
]
)
- Each element in the group is either:
- a single exception
- another exception group
# Example of grouping multiple exceptions
try:
raise BaseExceptionGroup("Multiple errors", [ValueError("v"), TypeError("t")])
except BaseExceptionGroup as eg:
print("Caught group:", eg)
# Output: Caught group: Multiple errors (2 sub-exceptions)
Caught group: Multiple errors (2 sub-exceptions)
Example: Raising an Exception Group¶
def run_tasks():
errors = []
# Task 1: division by zero
try:
10 / 0
except Exception as e:
errors.append(e)
# Task 2: invalid conversion
try:
int("abc")
except Exception as e:
errors.append(e)
# If multiple tasks failed, group them
if errors:
raise ExceptionGroup("Multiple task failures in Intensity Coding", errors)
try:
run_tasks()
except ExceptionGroup as eg:
print(eg)
print("Exception Group Caught:")
for err in eg.exceptions:
print(" -", repr(err))
# Expected Output:
#Multiple task failures in Intensity Coding (2 sub-exceptions)
# Exception Group Caught:
# - ZeroDivisionError('division by zero')
# - ValueError("invalid literal for int() with base 10: 'abc'")
Multiple task failures in Intensity Coding (2 sub-exceptions)
Exception Group Caught:
- ZeroDivisionError('division by zero')
- ValueError("invalid literal for int() with base 10: 'abc'")
Using except* for Selective Handling¶
- Python 3.11 introduced a new handler:
except* ExceptionType:
It was designed specifically for working with exception groups, where several independent errors are raised together.
The normal except block is not enough in this situation because it attempts to match an entire exception group. In contrast, except* extracts only the exceptions of a particular type, handles them, and leaves the rest untouched.
This makes it possible to respond differently to each type of failure even when they appear in the same group.
How except* Behaves
When an exception group reaches the except* blocks, Python performs the following steps:
- Scan the group for exceptions matching the target type.
- Split the group into subgroups:
- one subgroup containing only the matching exceptions,
- another containing everything else.
- Run the block using the matching subgroup.
- Re-raise the remainder, unless later except* blocks handle them.
Example: Selectively Handling Different Errors
# Example: selective handling
try:
# Construct a group with multiple different exception types
raise ExceptionGroup(
"Example group",
[
ValueError("invalid configuration"),
TypeError("unsupported parameter type"),
ZeroDivisionError("division by zero occurred")
]
)
# Handle only the ValueError inside the group
except* ValueError as group_val:
print("Handled ValueError subgroup:", group_val)
# Handle only the ZeroDivisionError inside the group
except* ZeroDivisionError as group_zero:
print("Handled ZeroDivisionError subgroup:", group_zero)
# Remaining exceptions (e.g., TypeError) propagate upward
Handled ValueError subgroup: Example group (1 sub-exception) Handled ZeroDivisionError subgroup: Example group (1 sub-exception)
--------------------------------------------------------------------------- ExceptionGroup Traceback (most recent call last) /tmp/ipython-input-600235792.py in <cell line: 0>() 2 try: 3 # Construct a group with multiple different exception types ----> 4 raise ExceptionGroup( 5 "Example group", 6 [ ExceptionGroup: Example group (1 sub-exception)
- Explained Output : The actual printed lines will be:
Handled ValueError subgroup: Example group (1 sub-exception)
Handled ZeroDivisionError subgroup: Example group (1 sub-exception)
- After these handlers run, only one exception remains:
TypeError("unsupported parameter type")
- Because no
except* TypeErroris present, Python re-raises a new exception group containing just that TypeError:
ExceptionGroup: Example group (1 sub-exception)
- This shows how each except* block isolates and processes only the types it is designed to handle.