Previous | Tutorial index | Next
Explain different types of errors and exceptions that may occur in a Python program.
No matter how experienced a programmer you are, errors are an inevitable part of software development. The key difference between a novice and an expert is not the absence of errors, but the ability to anticipate, understand, and gracefully handle them.
In this tutorial, we will build a solid foundation for understanding what can go wrong in a Python program. We will classify different types of errors, explain the concept of exceptions, explore Python's exception hierarchy, and discuss why proper exception handling is a hallmark of professional, robust software.
Before diving into the content, familiarise yourself with these key terms:
| Term | Definition |
|---|---|
| Syntax Error | An error that occurs when the Python interpreter cannot parse your code because it violates the language's grammar rules. |
| Logical Error | An error where the code runs but produces incorrect results due to a flaw in the programmer's logic. |
| Runtime Error (Exception) | An error that occurs during program execution when an unexpected condition is encountered. |
| Exception | An event that disrupts the normal flow of a program's instructions; represented as an object in Python. |
| Traceback | A detailed report printed when an unhandled exception occurs, showing the call stack and error details. |
| Exception Hierarchy | The class structure where all exceptions inherit from BaseException; most inherit from Exception. |
| Call Stack | The sequence of function calls that led to an error, shown in the traceback. |
Imagine you are a barista following a recipe to make a latte. The recipe is like your Python code.
Syntax Error: You misread "add 2 shots of espresso" as "add 2 shots of espresso and milk" and pour milk into the espresso before steaming it. The drink is fundamentally wrong from the start – the code is malformed and cannot be understood.
Logical Error: You follow the recipe exactly, but the recipe itself has a mistake: it says "add 3 tablespoons of sugar" instead of "3 teaspoons." The drink is made correctly according to the recipe, but it tastes terrible – the code runs but produces the wrong result.
Exception: You are making the latte correctly, but suddenly the espresso machine runs out of coffee beans. Your process is interrupted by an external, unpredictable event – a runtime exception.
We divide errors into three main categories:
These occur when the interpreter cannot parse your code because it violates Python's grammar.
Key Characteristics:
Example:
print("Hello, World!" # Missing closing parenthesis → SyntaxError
Another example:
if x > 5
print("x is greater than 5") # Missing colon → SyntaxError
How to fix: Correct the syntax.
The code runs without crashing, but produces incorrect results.
Key Characteristics:
Example:
numbers = [10, 20, 30]
average = sum(numbers) # Forgot to divide by len(numbers)
print(f"The average is: {average}") # Prints 60 instead of 20
How to fix: Debugging – trace through logic, use print statements, or use a debugger.
These occur while the program is running, when an unexpected condition is encountered.
Key Characteristics:
Example:
x = 10
y = 0
result = x / y # ZeroDivisionError
Summary Table:
| Error Type | Detection Time | Program Execution | Example |
|---|---|---|---|
| Syntax Error | Before execution (parsing) | Does not start | print("Hello" (missing parenthesis) |
| Logical Error | During execution | Runs, but wrong results | average = sum(numbers) instead of sum/len |
| Runtime Error | During execution | Runs until the error, then crashes | 10 / 0 |
An exception is an event that disrupts the normal flow of the program's instructions. In Python, exceptions are objects.
raise).try...except block is found, the program can recover.ValueError)FileNotFoundError)ZeroDivisionError)IndexError)KeyError)When an exception is unhandled, Python prints a traceback – a detailed report.
Example traceback:
Traceback (most recent call last):
File "program.py", line 8, in <module>
result = divide_numbers(10, 0)
File "program.py", line 4, in divide_numbers
return a / b
ZeroDivisionError: division by zero
What the traceback tells you:
ZeroDivisionError).division by zero).All exceptions inherit from a base class. Understanding this hierarchy helps you write more precise handlers.
BaseException
├── SystemExit
├── KeyboardInterrupt
├── GeneratorExit
└── Exception
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── OSError
│ ├── FileNotFoundError
│ └── PermissionError
├── NameError
├── TypeError
├── ValueError
└── ... (many more)
BaseException: Base for all exceptions; includes system‑exiting exceptions.Exception: Base for all built‑in, non‑system‑exiting exceptions. Most user‑defined exceptions should inherit from Exception.ZeroDivisionError, OverflowError, etc.IndexError, KeyError.FileNotFoundError, PermissionError, etc.You can catch a more general exception to handle a family of errors:
try:
result = 10 / 0
except ZeroDivisionError:
print("Specific: Cannot divide by zero")
except ArithmeticError:
print("General: Some arithmetic error")
Best practice: place more specific exceptions first.
Handling exceptions is about writing professional, robust software.
Without handling (crashes):
filename = input("Enter filename: ")
file = open(filename, "r") # FileNotFoundError if file missing
With handling (recovers):
try:
filename = input("Enter filename: ")
with open(filename, "r") as file:
content = file.read()
except FileNotFoundError:
print("File not found. Please check the name.")
except Exception as e:
print(f"Unexpected error: {e}")
finally:
print("Thank you for using the file reader.")
| Concept | Description |
|---|---|
| Syntax Error | Code violates grammar; program cannot run. |
| Logical Error | Code runs but gives wrong results; no error message. |
| Runtime Exception | Code runs until an error occurs; crashes unless handled. |
| Traceback | Shows call stack and error details for debugging. |
| Exception Hierarchy | BaseException → Exception → specific exceptions. Use specificity to catch appropriately. |
| Why Handle? | Prevents crashes, provides clear messages, separates concerns, improves robustness. |
Test your understanding of the concepts covered in this tutorial. Answer each question, then click to reveal the correct answer.
Q1: What type of error occurs when the Python interpreter cannot parse your code?
Q2: What is the output of the following code?
numbers = [1, 2, 3]
total = sum(numbers)
print(total / 0)
ZeroDivisionError exceptionQ3: True or False: A logical error will cause the program to crash with an error message.
Q4: Which of the following is the base class for all built‑in, non‑system‑exiting exceptions in Python?
BaseExceptionExceptionRuntimeErrorArithmeticErrorQ5: What is a traceback used for?
Q6: Which of the following is NOT a benefit of exception handling?
Q7: Consider the exception hierarchy. Which of the following catches both ZeroDivisionError and OverflowError?
except ZeroDivisionError:except ArithmeticError:except LookupError:except OSError:Q8: Given the following code, what will be printed?
try:
x = int("abc")
except ValueError:
print("Value error caught!")
except TypeError:
print("Type error caught!")
except Exception:
print("General exception caught!")
Complete the exercises below to reinforce your understanding. Sample solutions are provided after each exercise.
Instructions: For each of the following code snippets, identify what type of error or exception would occur and explain why.
a)
for i in range(5)
print(i)
b)
def calculate_interest(principal, rate, time):
return principal * rate * time / 100
result = calculate_interest(1000, 5, 2)
print("The interest is:", result)
# Expected output: 100, but it prints 100.0
c)
my_list = [1, 2, 3]
print(my_list[5])
d)
student = {"name": "Alice", "age": 25}
print(student["grade"])
Instructions: Consider the following code and traceback. Answer the questions below.
# File: calculator.py
def divide(a, b):
return a / b
def average(numbers):
total = sum(numbers)
count = len(numbers)
result = divide(total, count)
return result
data = [10, 20, 0, 30]
print(average(data))
Traceback:
Traceback (most recent call last):
File "calculator.py", line 11, in <module>
print(average(data))
File "calculator.py", line 7, in average
result = divide(total, count)
File "calculator.py", line 2, in divide
return a / b
ZeroDivisionError: division by zero
Questions:
Instructions: Rewrite the following code to handle:
ValueError if the user enters non‑numeric input.ZeroDivisionError if the denominator is zero.except Exception as a safety net.Original Code:
numerator = int(input("Enter the numerator: "))
denominator = int(input("Enter the denominator: "))
result = numerator / denominator
print(f"Result: {result}")
def safe_divide():
try:
numerator = int(input("Enter the numerator: "))
denominator = int(input("Enter the denominator: "))
result = numerator / denominator
print(f"Result: {result}")
except ValueError:
print("Error: Please enter valid numeric values.")
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
safe_divide()
Instructions: Given the exception hierarchy snippet below, determine which except block(s) would catch the exception in each scenario.
Exception
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── IndexError
│ └── KeyError
└── ValueError
a)
try:
print([1, 2, 3][5])
except IndexError:
print("Index error caught")
except LookupError:
print("Lookup error caught")
except Exception:
print("General exception caught")
b)
try:
int("abc")
except LookupError:
print("Lookup error caught")
except ValueError:
print("Value error caught")
except Exception:
print("General exception caught")
c)
try:
10 / 0
except Exception:
print("General exception caught")
except ZeroDivisionError:
print("Zero division caught")
Answer the following questions in complete sentences. For essay questions, aim for 300–500 words. Sample answers are provided after each question.
1. Explain the difference between syntax errors, logical errors, and runtime exceptions. For each type, provide a real‑world scenario (not code) that illustrates the concept, and then give a Python code example.
Logical Error: Like following a recipe that tells you to add sugar instead of salt – the instructions are syntactically correct, but the outcome is wrong.
Code Example: average = sum(numbers) instead of sum(numbers) / len(numbers).
Runtime Exception: Like a power outage while you're cooking – the recipe is correct, but an external factor interrupts the process. In Python, dividing by zero or opening a missing file causes a runtime exception.
Code Example: x = 10 / 0.
2. The following program is supposed to read numbers from "data.txt", calculate their average, and print it. However, it contains multiple errors. Identify and fix all errors.
data_file = open(data.txt, "r")
lines = data_file.readlines()
total = 0
count = 0
for line in lines
number = int(line)
total += number
count = count + 1
average = total / count
print("The average is: average)
data_file.close()
Errors:
data.txt is not quoted as a string.for loop is missing a colon.line may contain newline characters; use .strip().count is zero, division by zero occurs.average variable is not correctly inserted into the print string.Fixed code:
try:
data_file = open("data.txt", "r")
except FileNotFoundError:
print("Error: data.txt not found.")
exit()
lines = data_file.readlines()
data_file.close()
total = 0
count = 0
for line in lines:
try:
number = int(line.strip())
total += number
count += 1
except ValueError:
print(f"Warning: Skipping invalid line: {line.strip()}")
if count == 0:
print("No valid numbers found.")
else:
average = total / count
print(f"The average is: {average}")
3. Write a Python function get_valid_integer(prompt, min_value, max_value) that repeatedly asks the user for an integer within the given range, handles ValueError and any other exception, and returns the valid integer.
def get_valid_integer(prompt, min_value, max_value):
while True:
try:
user_input = input(prompt)
value = int(user_input)
if value < min_value or value > max_value:
print(f"Error: Value must be between {min_value} and {max_value}.")
continue
return value
except ValueError:
print("Error: Please enter a valid integer.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
4. Given the following code, write the traceback that would be generated if the user enters "abc" for age, then explain what the traceback tells you, and finally modify the code to handle the exception gracefully.
def get_user_data():
name = input("Enter name: ")
age = int(input("Enter age: "))
return name, age
def display_user():
user_name, user_age = get_user_data()
print(f"User: {user_name}, Age: {user_age}")
def main():
display_user()
if __name__ == "__main__":
main()
Traceback:
Traceback (most recent call last):
File "program.py", line 14, in <module>
main()
File "program.py", line 10, in main
display_user()
File "program.py", line 6, in display_user
user_name, user_age = get_user_data()
File "program.py", line 3, in get_user_data
age = int(input("Enter age: "))
ValueError: invalid literal for int() with base 10: 'abc'
Explanation: The error occurred in get_user_data at line 3. int() tried to convert 'abc' to an integer, which failed. The call stack shows main() → display_user() → get_user_data(). The error type is ValueError.
Modified code to handle gracefully:
def get_user_data():
name = input("Enter name: ")
try:
age = int(input("Enter age: "))
except ValueError:
print("Invalid age. Please enter a number.")
return get_user_data() # retry recursively
return name, age
5. Discuss the role of exception handling in software development. Include the importance of graceful error recovery, the balance between catching early vs. letting exceptions propagate, when exceptions should be used for control flow (and when not), and the trade‑offs between try...except and conditional checks.
Exception handling is a cornerstone of professional software development. It allows programs to manage errors gracefully, preventing crashes and providing meaningful feedback to users. Without exception handling, a single missing file or invalid input could terminate an entire application, leading to a poor user experience.
One key decision is whether to catch exceptions early or let them propagate. The principle is to catch exceptions at a level where you can meaningfully handle them. Low‑level functions should not catch exceptions they cannot resolve; instead, they should let them propagate to higher layers where the appropriate recovery strategy is known. For example, a file‑reading function might catch FileNotFoundError and attempt to create a default file, while a higher‑level function might catch a broader RuntimeError and log it for debugging.
When it comes to using exceptions for control flow, the consensus is clear: exceptions should be used for exceptional conditions, not for normal program flow. Using try...except to check if a key exists in a dictionary instead of using get() or if key in dict is considered an antipattern because exceptions are expensive to raise and make the code harder to read.
The trade‑off between try...except and conditional checks depends on the situation. For rare, truly exceptional conditions (e.g., file not found when it should exist), try...except keeps the normal code path clean and separates error handling from business logic. For common, expected conditions (e.g., checking if a list is empty before accessing an element), conditionals are clearer and more performant.
In summary, good exception handling requires judgment: use try...except for recoverable errors in production, reserve assert for debugging, and rely on conditionals for anticipated states. This balance yields code that is both robust and maintainable.
6. Research the exception hierarchy in Python. What are the key differences between BaseException and Exception? Why is it generally considered bad practice to catch BaseException or use a bare except: statement? Provide examples of when you might actually want to catch BaseException.
The Python exception hierarchy is rooted in BaseException, from which all exceptions derive. BaseException includes system‑level exceptions like SystemExit (raised by sys.exit()) and KeyboardInterrupt (raised when the user presses Ctrl+C). Exception, on the other hand, is the base class for all built‑in, non‑system‑exiting exceptions. Most user‑defined exceptions should inherit from Exception.
Catching BaseException or using a bare except: is generally bad practice because it intercepts system‑level exceptions that are not meant to be caught. For example, catching KeyboardInterrupt prevents a user from terminating a stuck program, and catching SystemExit prevents a program from exiting cleanly. These exceptions are designed to control the program's lifecycle and should be allowed to propagate.
There are rare cases where catching BaseException is appropriate, such as when writing a system service that must clean up resources before exiting, or when implementing a debugger that needs to log all exceptions. However, in almost all cases, catching Exception is the correct choice, and catching BaseException should be reserved for specialized system‑level programming.