SomeQuiz

Course

Exception Handling

Founder

Developer

Sithija Nelusha

@snelusha

Python is a versatile programming language that allows developers to create robust and efficient applications. However, even the most experienced programmers encounter errors during the development process. Python provides a comprehensive exception handling mechanism that enables developers to gracefully handle these errors, improving the overall reliability and stability of their code.

Exception handling in Python involves catching and responding to errors that occur during program execution. Python categorizes errors into three main types: syntax errors, runtime errors, and logical errors. Each of these errors requires a different approach to handle effectively.

Syntax Errors

Syntax errors, also known as parsing errors, occur when the Python interpreter encounters invalid code that violates the language's syntax rules. These errors prevent the program from running at all. Common causes of syntax errors include missing colons, misplaced parentheses, and incorrect indentation.

print("Hi Something"
  • Output

In this case, the interpreter will raise a SyntaxError and provide a traceback indicating the location of the error. To fix this issue, the missing parenthesis should be added:

print("Hi Something")

Runtime Errors

Runtime errors, also referred to as exceptions, occur during the execution of a program. These errors can be caused by a variety of factors, such as incorrect input, division by zero, or accessing a non-existent file. Python provides a wide range of built-in exception classes to handle specific types of runtime errors.

Built-in Exceptions

  • SyntaxError: Raised when the Python interpreter encounters a syntax error due to invalid code.
# Missing closing parenthesis
print("something"
  • IndentationError: Raised when there is a problem with the indentation of code, such as inconsistent or incorrect spacing.
# Inconsistent indentation
if True:
print("something")
  • NameError: Raised when a local or global name is not found.
# Using an undefined variable
x = 5
print(y)
  • TypeError: Raised when an operation or function is applied to an object of inappropriate type.
# Adding an integer and a string
result = 10 + "20"
  • ValueError: Raised when a function receives an argument of correct type but an inappropriate value.
# Converting an invalid string to integer
age = int("Twenty")
  • IndexError: Raised when a sequence subscript is out of range.
# Accessing an element outside the list's range
numbers = [1, 2, 3]
print(numbers[5])
  • KeyError: Raised when a dictionary key is not found.
# Accessing a non-existent key in a dictionary
student = {"name": "Alice", "age": 20}
print(student["grade"])
  • FileNotFoundError: Raised when a file or directory is requested but cannot be found.
# Trying to open a non-existent file
file = open("nonexistent_file.txt", "r")
  • ImportError: Raised when an import statement fails to find and load a module.
# Importing a non-existent module
import non_existent_module
  • ZeroDivisionError: Raised when division or modulo operation is performed with zero as the divisor.
# Dividing by zero
result = 10 / 0
  • AttributeError: Raised when an attribute reference or assignment fails.
# Accessing a non-existent attribute
class Person:
    name = "Alice"
 
person = Person()
print(person.age)
  • OverflowError: Raised when the result of an arithmetic operation is too large to be expressed within the limits of a given numeric type.
# Performing an arithmetic operation that exceeds the limit
result = 2 ** 1000
  • MemoryError: Raised when the interpreter runs out of memory.
# Creating a large list that exceeds available memory
numbers = [0] * (10 ** 8)
  • RuntimeError: Raised when an error is detected that doesn't fall into any specific category.
# Raising a runtime error manually
raise RuntimeError("Something went wrong!")
  • KeyboardInterrupt: Raised when the user interrupts the program execution, typically by pressing Ctrl + C.
# Interrupting program execution with Ctrl+C
while True:
    pass
  • AssertionError: Raised when an assert statement fails.
# Asserting a condition that fails
age = 15
assert age >= 18, "Must be 18 or older

Handle Exceptions

When a runtime error occurs, it interrupts the normal flow of execution and raises an exception. If the exception is not caught and handled by an appropriate exception handler, the Python interpreter terminate the execution of the script. This behavior is intended to prevent the propagation of unhandled errors, which could lead to unexpected and undesirable consequences.

To prevent the script from terminating upon encountering a runtime error, exception handling mechanisms can be implemented using try-except blocks. By using try-except blocks, specific exceptions can be caught and handled, allowing the script to recover from errors and resume execution. This facilitates controlled error handling and prevents the script from terminating abruptly.

Structure of try-except block

try:
    # Code that might raise an exception
    # ...
except ExceptionType1:
    # Handle ExceptionType1
    # ...
except ExceptionType2:
    # Handle ExceptionType2
    # ...
else:
    # Code to execute when the try block succeeds (no exceptions raised)
    # ...
finally:
    # Cleanup tasks or actions to be performed regardless of exceptions
    # ...
  • The code that may raise an exception is placed within the try block.
  • If an exception of ExceptionType1 occurs, the corresponding except ExceptionType1 block is executed, allowing for handling that specific exception.
  • Similarly, if an exception of ExceptionType2 occurs, the corresponding except ExceptionType2 block is executed to handle that particular exception.
  • If no exceptions are raised in the try block, the code within the else block is executed. This block is optional and provides a way to run additional code when the try block succeeds.
  • Finally, regardless of whether exceptions occurred or not, the code within the finally block is executed. This block is also optional and is typically used for cleanup tasks or actions that need to be performed, such as closing files or releasing resources.

Raise Statement

The raise statement in Python is used to explicitly raise an exception, thereby interrupting the normal flow of the program. By raising an exception, developers can handle unexpected situations, validate input, and ensure that errors are properly handled and reported.

Syntax

Here, ExceptionType represents the specific exception class that you want to raise. Optionally, you can pass additional arguments to the exception class.

Raising Built-in Exceptions

Python provides a wide range of built-in exception classes that cover various error scenarios. These exceptions are part of the standard library and can be raised using the 'raise' statement.

raise ValueError("Invalid value provided")
raise FileNotFoundError("File not found")

By specifying the appropriate exception class and providing a meaningful error message, you can communicate the exact nature of the error to the developer or end user.

Creating Custom Exceptions

In addition to the built-in exceptions, Python allows you to define custom exception classes. This can be useful when you want to handle specific types of errors that are not covered by the built-in exceptions.

import math
 
def calculate_square_root(number):
    if number < 0:
        raise ValueError("Cannot calculate square root of a negative number.")
    else:
        return math.sqrt(number)
        
try:
    result = calculate_square_root(-4)
except ValueError as e:
    print(e)
  • Output
Cannot calculate square root of a negative number.
  • In this case, the calculate_square_root() function call within the try block passes a negative number (-4) as an argument. Since this violates the condition in the function, a ValueError exception is raised.

  • The except ValueErroras e block catches the ValueError exception specifically. The as e part assigns the caught exception object to the variable e, allowing us to access its properties.

  • Within the except block, the code print(e) is executed, which prints the error message associated with the ValueError exception. In this case, it will display "Cannot calculate square root of a negative number."

  • If there were additional code following the except block, it would execute regardless of whether an exception occurred or not.

Logical Errors

Logical errors, also known as semantic errors, occur when the program runs without raising any exceptions but produces incorrect results. These errors are often harder to detect and fix since they are not caused by syntax or runtime issues but rather flawed program logic.

# Logical Error: Incorrect condition
age = 25
 
if age > 18 or age < 65:
    print("You are eligible to vote.")
else:
    print("You are not eligible to vote.")

the code intends to check if a person is eligible to vote based on their age. However, there is a logical error in the condition. The condition age > 18 or age < 65 will evaluate to True for any age value, including ages below 18 and above 65.

To fix this logical error, the condition should be modified to use the logical AND (and) operator instead of the logical OR (or) operator:

age = 25
 
if age >= 18 and age <= 65:
    print("You are eligible to vote.")
else:
    print("You are not eligible to vote.")

By using the logical AND operator, the condition ensures that the age is both greater than or equal to 18 and less than or equal to 65 for the person to be eligible to vote. Logical errors can have significant implications on the correctness and reliability of the program, and careful attention to the logic is essential for accurate results.

Exception handling in Python is crucial for dealing with errors and unexpected situations during program execution. It allows developers to handle exceptions gracefully, recover from errors, and maintain program stability. By using try-except blocks, specific exceptions can be caught and appropriate actions can be taken. Exception handling improves the reliability, robustness, and user experience of Python programs.