Contents
Python Exception Handling
In Python programming, errors can be broadly classified into two types: Syntax Errors and Exceptions. Errors halt the program execution, while exceptions arise from unexpected events that disrupt the usual program flow but may still be handled to allow the program to continue.
Common Types of Python Exceptions
Python has several built-in exceptions for handling errors that may occur during code execution. Here are some frequent ones:
- SyntaxError: Raised when there is a syntax issue, like a missing colon or an unmatched parenthesis.
- TypeError: Occurs when an operation is applied to an inappropriate data type, such as adding an integer to a string.
- NameError: Triggered when a variable or function name is not found.
IndexError: Occurs when an index is out of range in a list or other sequence. - KeyError: Raised when a key is not found in a dictionary.
- ValueError: Occurs when a function receives an argument with the right type but an inappropriate value.
- AttributeError: Triggered when an attribute or method is not found on an object.
- IOError: Raised for input/output errors, such as issues reading or writing a file.
- ZeroDivisionError: Occurs when dividing a number by zero.
- ImportError: Triggered when a module fails to import.
Difference Between Syntax Errors and Exceptions
Syntax Errors: Caused by improper syntax and will terminate the program.
amount = 5000
if amount > 1000
print("Eligible to make a purchase")
Output:
SyntaxError: expected ':'
Exceptions: These arise from runtime errors in syntactically correct code, which can change the program’s normal flow.
value = 100
result = value / 0
print(result)
Output:
ZeroDivisionError: division by zero
Handling Exceptions with try and except
In Python, you use try
and except
blocks to catch and handle exceptions.
Example:
data = [1, 2, 3]
try:
print("Second element:", data[1])
print("Fourth element:", data[3])
except IndexError:
print("An error occurred: Index out of range")
Output:
Second element: 2
An error occurred: Index out of range
Catching Specific Exceptions
You can use multiple except
clauses to handle different types of exceptions.
def calculate(a):
if a < 5:
result = a / (a - 4)
print("Result:", result)
try:
calculate(3)
calculate(5)
except ZeroDivisionError:
print("Division by zero error occurred and handled")
except NameError:
print("NameError occurred and handled")
Output:
Division by zero error occurred and handled
try
with else
Clause
The else
clause runs only if the try
block does not raise any exceptions.
name = 'Sam'
age = 25
print(f"Hello, My name is {name} and I'm {age} years old.")
Output:
-5.0
Division resulted in zero denominator
finally Clause in Python
The finally
block is always executed after try
and except
blocks, regardless of whether an exception occurs.
num = int(input("Enter a value: "))
add = num + 5
print("The sum is %d" % add)
Output:
Cannot divide by zero
Execution complete
Raising Exceptions
The raise
statement allows you to force a specific exception. You can specify an error message when raising an exception.
try:
raise NameError("This is a custom NameError")
except NameError:
print("A NameError occurred")
raise
Output:
A NameError occurred
Traceback (most recent call last):
...
NameError: This is a custom NameError
Advantages of Exception Handling
- Improves program reliability: Helps in avoiding program crashes from unexpected errors.
- Simplifies error handling: Keeps error management separate from main logic, making the code more readable.
- Produces cleaner code: Reduces complex conditional checks for error detection.
- Eases debugging: Exception traceback indicates the error location, simplifying debugging.
Disadvantages of Exception Handling
- Performance overhead: Handling exceptions can be slower than using conditional checks.
- Increases code complexity: Multiple exceptions can add complexity, especially with varied handling methods.
- Potential security risks: Unhandled exceptions might reveal sensitive information or create vulnerabilities, so careful handling is essential.
User-defined Exceptions in Python with Examples
Custom exceptions in Python with practical examples is as follows.
class CustomError(Exception):
pass
raise CustomError("Example of Custom Exceptions in Python")
Output:
CustomError: Example of Custom Exceptions in Python
Python raises errors and exceptions when something goes wrong, potentially causing abrupt program termination. However, with try-except
blocks, Python provides robust exception handling to prevent such interruptions. Common exceptions include IndexError
, ImportError
, IOError
, ZeroDivisionError
, TypeError
, and FileNotFoundError
.
Creating User-Defined Exceptions in Python
Custom exceptions should inherit from the Exception
class, directly or indirectly. While it’s not required, custom exceptions often follow the naming convention of ending with “Error,” similar to Python’s standard exceptions.
Example:
# A Python program to create a user-defined exception
# MyError class inherits from Exception
class MyError(Exception):
# Constructor to initialize the error value
def __init__(self, value):
self.value = value
# __str__ method to return the error message
def __str__(self):
return repr(self.value)
try:
raise MyError(3 * 2)
# Handling the exception
except MyError as error:
print('A new exception occurred:', error.value)
Output:
Customizing Exception Classes
For more details about Exception class, try running:
python
Copy code
Customizing Exception Classes
For more details about Exception
class, try running:
help(Exception)
Example 1: User-Defined Exception with Multiple Inheritance
Below is an example with a base class Error
inherited by user-defined classes to handle specific custom exceptions.
# Defining a base class for exceptions
class Error(Exception):
"""Base class for other exceptions"""
pass
class ZeroDivisionErrorCustom(Error):
"""Raised when the input value is zero"""
pass
try:
num = int(input("Enter a number: "))
if num == 0:
raise ZeroDivisionErrorCustom
except ZeroDivisionErrorCustom:
print("Input value is zero, please try again!")
Output:
Enter a number: 0
Input value is zero, please try again!
Example 2: Deriving Errors from Superclass Exception
When a module requires specific exception handling, a base class for those exceptions can be defined. Additional subclasses allow for distinct error conditions.
# Define a base exception class
class Error(Exception):
pass
class TransitionError(Error):
# Raised when an invalid state transition occurs
def __init__(self, prev, next, msg):
self.prev = prev
self.next = next
self.msg = msg
try:
raise TransitionError(2, 6, "Transition Not Allowed")
except TransitionError as error:
print('Exception occurred:', error.msg)
Output:
Exception occurred: Transition Not Allowed
Using Standard Exceptions as a Base Class
The following example demonstrates using RuntimeError
as a base class for custom exceptions, like NetworkError
.
# NetworkError is derived from RuntimeError
class NetworkError(RuntimeError):
def __init__(self, message):
self.message = message
try:
raise NetworkError("Network connection lost")
except NetworkError as e:
print(e.message)
Output:
Network connection lost
Advantages of Exception Handling
- Reliability: Manages unexpected errors, ensuring program stability.
- Simplified Error Management: Separates error-handling code from main logic.
- Cleaner Code: Avoids nested conditionals by handling errors systematically.
- Debugging Ease: Tracebacks help in pinpointing error locations quickly.
Disadvantages of Exception Handling
- Performance Overhead: Handling exceptions is slightly slower than conditional checks.
- Complexity: Can increase code complexity, especially with multiple exception types.
- Security Risks: Improperly managed exceptions may expose sensitive information, so they must be handled with caution.
Built-in Exceptions in Python
In Python, all instances must be derived from the BaseException
class, and no two unrelated exception classes are considered equivalent, even if they share the same name. The Python interpreter or built-in functions can raise these exceptions.
You can view built-in exceptions, functions, and attributes using the locals()
function:
>>> locals()['__builtins__']
Base Classes
The following exceptions serve primarily as base classes for other exceptions:
1. BaseException
: This is the base class for all built-in exceptions. It is not intended for direct inheritance by user-defined classes, which should inherit from Exception
. This class uses str()
to create a string representation of the exception based on its arguments. An empty string is returned if there are no arguments.
Attributes:
- args: A tuple of arguments passed to the exception constructor.
- with_traceback(tb): Sets tb as the new traceback for the exception and returns the exception object.
try:
...
except SomeException:
tb = sys.exc_info()[2]
raise OtherException(...).with_traceback(tb)
Exception
: This is the base class for all non-system-exiting exceptions. User-defined exceptions should inherit from this class.
2. ArithmeticError: Base class for exceptions related to arithmetic operations, such as:
OverflowError
ZeroDivisionError
FloatingPointError
Example:
try:
a = 10 / 0
print(a)
except ArithmeticError:
print("Arithmetic exception occurred.")
Output:
Arithmetic exception occurred.
3. BufferError: Raised when an operation related to buffers cannot be completed.
4. LookupError: Base class for exceptions raised when a key or index used on a mapping or sequence is invalid. Examples include:
KeyError
IndexError
Example:
try:
a = [1, 2, 3]
print(a[3])
except LookupError:
print("Index out of range.")
Output:
Index out of range.
Concrete Exceptions
Here are some of the more common exceptions:
AssertionError
: Raised when anassert
statement fails.
class MyClass:
pass
obj = MyClass()
print(obj.some_attribute)
Output:
AttributeError: 'MyClass' object has no attribute 'some_attribute'
EOFError
: Raised wheninput()
reaches the end of file condition.
while True:
data = input('Enter name: ')
print('Hello', data)
If an EOF is encountered, the interpreter will raise:
EOFError: EOF when reading a line
FloatingPointError
: Raised for floating-point operation failures when Python is configured to raise this error.GeneratorExit
: Raised when a generator or coroutine is closed.
def generator():
try:
yield 1
except GeneratorExit:
print('Generator is exiting')
g = generator()
next(g)
g.close()
Output:
ImportError: No module named 'non_existent_module'
IndexError
: Raised for sequence indexes out of range.
lst = [1, 2]
print(lst[3])
Output:
IndexError: list index out of range
KeyError
: Raised when a mapping (dictionary) key is not found
dct = {'a': 1}
print(dct['b'])
Output:
KeyError: 'b'
KeyboardInterrupt
: Raised when the user interrupts execution (e.g., by pressing Ctrl+C).
try:
input("Press Ctrl+C to interrupt.")
except KeyboardInterrupt:
print("Execution interrupted by user.")
MemoryError
: Raised when an operation runs out of memory.NameError
: Raised when a local or global name is not found.
print(unknown_variable)
Output:
NameError: name 'unknown_variable' is not defined
- NotImplementedError: Raised when an abstract method in a user-defined class is not implemented in derived classes.
- OSError: Raised for operating system-related errors.
- OverflowError: Raised for arithmetic operations that exceed limits.
- RecursionError: Raised when the maximum recursion depth is exceeded.
- ReferenceError: Raised when a weak reference proxy is used to access a referent that has been garbage-collected.
- RuntimeError: Raised for errors that do not fall into any other category.
- StopIteration: Raised to indicate the end of an iterator.
- SyntaxError: Raised when a syntax error is encountered.
eval('x === y')
Output:
SyntaxError: invalid syntax
- SystemError: Raised for internal interpreter errors.
- SystemExit: Raised by the sys.exit() function.
- TypeError: Raised when an operation is applied to an object of inappropriate type.
5 + 'a'
Output:
TypeError: unsupported operand type(s) for +: 'int' and 'str'
- UnboundLocalError: A subclass of NameError, raised when a reference is made to a local variable that has not been assigned.
- UnicodeError: Raised for Unicode-related encoding or decoding errors.
- ValueError: Raised when a function receives an argument of correct type but invalid value.
int('a')
Output:
ValueError: invalid literal for int() with base 10: 'a'
ZeroDivisionError
: Raised when division or modulo by zero occurs.
print(1 / 0)
Output:
ZeroDivisionError: division by zero
In Python, errors can be classified as syntax errors and exceptions. Syntax errors occur due to incorrect syntax, stopping the program’s execution immediately. Exceptions, however, are raised by specific events during execution, changing the program’s normal flow.
Some commonly encountered exceptions include:
IOError
: Raised when a file operation (such as open) fails.KeyboardInterrupt:
Raised when the user interrupts program execution, typically by pressing Ctrl+C.ValueError
: Occurs when a function receives an argument of the right type but inappropriate value.EOFError
: Raised when theinput()
function hits an end-of-file condition.ImportError
: Raised when an import statement fails to find the specified module.
Try-Except in Python
The try
and except
statements help manage exceptions in Python. The try
block contains the code that may throw an exception, and the except
block handles the exception if it occurs.
try:
# Code to execute
except:
# Code to execute if an error occurs
How try
Works
1. Python executes the code within the try block first.
2. If no exception is raised, the except block is skipped.
3. If an exception occurs, Python jumps to the except block.
4. If the exception is not handled, it will propagate to any outer try blocks, and, if unhandled, will stop execution.
5. Multiple except blocks can be used to handle different exceptions.
Example 1: Code runs without any exceptions, so only the try
block executes.
def divide(x, y):
try:
result = x // y
print("The result is:", result)
except ZeroDivisionError:
print("Error: Division by zero is undefined.")
divide(10, 2)
Output:
The result is: 5
Catching Specific Exceptions with as
You can also catch and display the specific type of error that occurs by using Exception as
.
def divide(x, y):
try:
result = x // y
print("The result is:", result)
except Exception as e:
print("An error occurred:", e)
divide(10, 'A')
divide(10, 0)
Output:
An error occurred: unsupported operand type(s) for //: 'int' and 'str'
An error occurred: integer division or modulo by zero
Using else
with try
–except
In Python, you can use an else
clause with try
–except
blocks. The else
block executes only if no exceptions are raised in the try
block.
try:
# Code to execute
except:
# Code to execute if an error occurs
else:
# Code to execute if no exception occurs
Example:
def safe_divide(a, b):
try:
c = (a + b) // (a - b)
except ZeroDivisionError:
print("Error: Division by zero.")
else:
print("The result is:", c)
safe_divide(5, 3)
safe_divide(3, 3)
Output:
The result is: 4
Error: Division by zero.
finally
Keyword in Python
The finally
block in Python is always executed after the try
and except
blocks, whether or not an exception occurs. It is often used for cleanup actions.
try:
# Code to execute
except:
# Code to execute if an error occurs
else:
# Code to execute if no exception occurs
finally:
# Code that always executes
Example:
try:
k = 10 // 0 # Raises a ZeroDivisionError
print(k)
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
finally:
print("This block always executes.")
Output:
Error: Cannot divide by zero.
This block always executes.