Previous | Tutorial index | Next
TypeError, NameError, RuntimeError, OSError, ValueError, ZeroDivisionError, AssertionError, FileNotFoundError.Python provides a rich set of built-in exceptions that cover a wide range of error conditions. Understanding these exceptions is crucial for writing robust code and for effective debugging. When an exception occurs, Python raises an object of a specific exception class, and knowing which class it belongs to gives you immediate insight into what went wrong.
In this tutorial, we will explore eight of the most common built-in exceptions. For each exception, we will:
Before diving into the content, familiarise yourself with these key terms:
| Term | Definition |
|---|---|
| NameError | Raised when a local or global name is not found. |
| TypeError | Raised when an operation or function is applied to an object of inappropriate type. |
| ValueError | Raised when a function receives an argument of the correct type but inappropriate value. |
| ZeroDivisionError | Raised when the second argument of a division or modulo operation is zero. |
| FileNotFoundError | Raised when a file or directory is requested but does not exist. |
| OSError | Raised when a system-related operation causes an error. |
| RuntimeError | A general-purpose exception for errors that don't fit other categories. |
| AssertionError | Raised when an assert statement fails. |
Before diving into individual exceptions, let's understand how they relate to each other:
BaseException
βββ Exception
βββ NameError
βββ TypeError
βββ ValueError
βββ ArithmeticError
β βββ ZeroDivisionError
βββ LookupError
β βββ IndexError
β βββ KeyError
βββ OSError
β βββ FileNotFoundError
βββ RuntimeError
βββ AssertionError
Key Insight: All the exceptions we'll study in this tutorial inherit from Exception (which itself inherits from BaseException). This means you can catch any of them using except Exception: if you want a general handler, but it's always better to catch specific exceptions first.
A NameError is raised when Python encounters a name (a variable, function, class, or module name) that it cannot find in the current scope. This typically happens when:
Example 1: Using an undefined variable
print(my_variable) # NameError: name 'my_variable' is not defined
Example 2: Misspelling a function name
prnt("Hello") # NameError: name 'prnt' is not defined. Did you mean 'print'?
Example 3: Variable out of scope
def my_function():
inside_var = "I'm inside"
my_function()
print(inside_var) # NameError: name 'inside_var' is not defined
Example 4: Forgetting to import a module
sqrt(16) # NameError: name 'sqrt' is not defined
# Correct way:
import math
print(math.sqrt(16)) # Works fine
| Scenario | Example |
|---|---|
| Typo in variable name | user_naem instead of user_name |
| Using variable before assignment | count += 1 when count wasn't initialized |
| Forgetting to define a function | Calling calculate() before defining it |
| Misspelling a built-in function | lenght() instead of len() |
try:
print(undefined_variable)
except NameError:
print("Error: A variable is being used before it was defined.")
Initialize variables before use:
count = 0 # Initialize
count += 1 # Now safe
Use descriptive variable names to avoid typos:
user_name = "Alice" # Clear and less prone to typos
Use an IDE with autocompletion to catch typos early.
Check variable scope: Understand the difference between local and global scope.
A TypeError is raised when an operation or function is applied to an object of an inappropriate type. This is Python's way of saying, "I don't know how to do this with what you've given me."
Example 1: Adding incompatible types
result = "Hello" + 5 # TypeError: can only concatenate str (not "int") to str
Example 2: Calling a non-callable object
my_number = 42
my_number() # TypeError: 'int' object is not callable
Example 3: Using an incorrect number of arguments
def greet(name):
print(f"Hello, {name}")
greet("Alice", "Bob") # TypeError: greet() takes 1 positional argument but 2 were given
Example 4: Iterating over a non-iterable
for item in 42: # TypeError: 'int' object is not iterable
print(item)
Example 5: Using a non-subscriptable type
my_number = 42
print(my_number[0]) # TypeError: 'int' object is not subscriptable
| Operation | Incompatible Types | Error Message |
|---|---|---|
| Concatenation | str + int |
can only concatenate str (not "int") to str |
| Multiplication | str * str |
can't multiply sequence by non-int of type 'str' |
| Function Call | Calling an integer | 'int' object is not callable |
| Subscription | Indexing an integer | 'int' object is not subscriptable |
| Iteration | Iterating over an integer | 'int' object is not iterable |
try:
result = "Hello" + 5
except TypeError:
print("Error: Cannot add a string and an integer.")
result = "Hello" + str(5) # Convert to string
print(f"Fixed result: {result}")
Check types using type() or isinstance():
if isinstance(value, int):
print(value + 10)
else:
print("Expected an integer")
Convert types explicitly:
number_str = "42"
number_int = int(number_str) # Convert before use
result = number_int + 10
Use Python's duck typing carefully: Write functions that work with any type that supports the required operations.
A ValueError is raised when an operation or function receives an argument with the right type but an inappropriate value. The type is correct, but the specific value is not acceptable.
Example 1: Converting invalid string to integer
number = int("abc") # ValueError: invalid literal for int() with base 10: 'abc'
Example 2: Finding a substring that doesn't exist
text = "Hello World"
position = text.index("xyz") # ValueError: substring not found
Example 3: Using an invalid mathematical operation
import math
result = math.sqrt(-1) # ValueError: math domain error
Example 4: Removing an element that doesn't exist
my_list = [1, 2, 3]
my_list.remove(5) # ValueError: list.remove(x): x not in list
| Scenario | Example |
|---|---|
| Invalid numeric conversion | int("12.5") (a float string without decimal points) |
| Out-of-range in math functions | math.log(0) or math.sqrt(-1) |
| Invalid date/time parsing | datetime.strptime("2024-13-01", "%Y-%m-%d") |
| Empty sequence operations | max([]) (empty iterable) |
try:
user_age = int(input("Enter your age: "))
except ValueError:
print("Error: Please enter a valid integer.")
# You could retry, use a default value, or exit gracefully
Validate input before conversion:
user_input = input("Enter a number: ")
if user_input.isdigit():
number = int(user_input)
else:
print("Invalid input")
Use try...except for conversions:
try:
number = int(user_input)
except ValueError:
number = 0 # Default value
Check for conditions before using functions:
value = -1
if value >= 0:
result = math.sqrt(value)
else:
print("Cannot compute square root of negative number")
| Aspect | ValueError | TypeError |
|---|---|---|
| What's wrong | The value is incorrect | The type is incorrect |
| Example | int("abc") |
"abc" + 5 |
| Type is | Correct (string) | Incorrect (string + int) |
| Value is | Incorrect (not numeric) | N/A |
A ZeroDivisionError is raised when you attempt to divide a number by zero in a division or modulo operation. This is a specific type of ArithmeticError.
Example 1: Division by zero
result = 10 / 0 # ZeroDivisionError: division by zero
Example 2: Floor division by zero
result = 10 // 0 # ZeroDivisionError: integer division or modulo by zero
Example 3: Modulo by zero
result = 10 % 0 # ZeroDivisionError: integer division or modulo by zero
def safe_divide(numerator, denominator):
try:
return numerator / denominator
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
return None # Or some other sentinel value
result = safe_divide(10, 0)
if result is not None:
print(f"Result: {result}")
Always check the denominator:
if denominator != 0:
result = numerator / denominator
else:
print("Cannot divide by zero")
Use a default value when denominator is zero:
result = numerator / denominator if denominator != 0 else 0
In data processing, check for empty datasets:
if len(data) > 0:
average = sum(data) / len(data)
else:
average = 0
A FileNotFoundError is raised when you try to open or manipulate a file or directory that doesn't exist. It is a subclass of OSError.
Example 1: Opening a non-existent file
file = open("non_existent_file.txt", "r") # FileNotFoundError: [Errno 2] No such file or directory
Example 2: Removing a non-existent file
import os
os.remove("non_existent_file.txt") # FileNotFoundError: [Errno 2] No such file or directory
Example 3: Accessing a directory that doesn't exist
import os
os.listdir("non_existent_directory") # FileNotFoundError: [Errno 2] No such file or directory
PermissionError, which is also an OSError).try:
with open("config.txt", "r") as file:
content = file.read()
except FileNotFoundError:
print("Error: The configuration file could not be found.")
# You could create a default file or use default values
Check if a file exists before opening it:
import os
if os.path.exists("config.txt"):
with open("config.txt", "r") as file:
content = file.read()
else:
print("File not found")
Use try...except for robust file operations.
Provide clear error messages to help users locate the issue.
| Exception | Cause |
|---|---|
FileNotFoundError |
File doesn't exist |
PermissionError |
No permission to access the file |
IsADirectoryError |
Expected a file, got a directory |
NotADirectoryError |
Expected a directory, got a file |
TimeoutError |
Operation timed out |
An OSError is raised when a system-related operation (like file operations, I/O, or system calls) fails. It's the parent class for many system-related exceptions, including FileNotFoundError, PermissionError, and others.
Example 1: Opening a file without permission
file = open("/root/secret.txt", "r") # PermissionError (subclass of OSError)
Example 2: Creating a file in a read-only directory
file = open("/readonly_dir/newfile.txt", "w") # OSError: [Errno 30] Read-only file system
Example 3: Removing a file that is locked
import os
os.remove("locked_file.txt") # OSError: [Errno 13] Permission denied
try:
with open("important_data.txt", "w") as file:
file.write("Sensitive data")
except OSError as e:
print(f"An operating system error occurred: {e}")
print(f"Error number: {e.errno}")
print(f"Error message: {e.strerror}")
OSError
βββ BlockingIOError
βββ ChildProcessError
βββ ConnectionError
βββ FileExistsError
βββ FileNotFoundError
βββ InterruptedError
βββ IsADirectoryError
βββ NotADirectoryError
βββ PermissionError
βββ ProcessLookupError
βββ TimeoutError
Handle specific subclasses first, then fall back to OSError:
try:
# File operation
except FileNotFoundError:
print("File not found")
except PermissionError:
print("Permission denied")
except OSError as e:
print(f"Other OS error: {e}")
Use error codes for fine-grained handling:
try:
# Some operation
except OSError as e:
if e.errno == 28: # No space left on device
print("Disk is full")
else:
print(f"Unknown error: {e}")
A RuntimeError is a general-purpose exception that is raised when an error doesn't fall into any other specific category. It's often used when Python encounters an error condition that doesn't have a more specific exception class.
Example 1: Recursion depth exceeded
def recursive_function():
recursive_function() # Eventually raises RecursionError (subclass of RuntimeError)
recursive_function()
Example 2: Dictionary changed during iteration
my_dict = {"a": 1, "b": 2}
for key in my_dict:
my_dict["c"] = 3 # RuntimeError: dictionary changed size during iteration
RecursionError).try:
# Some operation that might raise RuntimeError
result = complex_operation()
except RuntimeError as e:
print(f"A runtime error occurred: {e}")
# Log the error for debugging
RuntimeError is intentionally broad. In practice, you should catch more specific exceptions when possible. Use RuntimeError as a last resort for unexpected errors that aren't covered by other exception types.
An AssertionError is raised when an assert statement fails. The assert statement is a debugging tool that tests a condition. If the condition is False, an AssertionError is raised.
Example 1: Basic assertion failure
x = 5
assert x == 10 # AssertionError
Example 2: Assertion with an error message
age = -5
assert age >= 0, "Age cannot be negative" # AssertionError: Age cannot be negative
Example 3: Asserting in a function
def calculate_average(numbers):
assert len(numbers) > 0, "List cannot be empty"
return sum(numbers) / len(numbers)
data = []
average = calculate_average(data) # AssertionError: List cannot be empty
DO use assert for:
DO NOT use assert for:
try:
assert x == 10, "x should be 10"
except AssertionError as e:
print(f"Assertion failed: {e}")
Assertions can be globally disabled by running Python with the -O (optimize) flag:
python -O script.py
This means you should never rely on assertions for critical security or business logicβuse proper error handling instead.
| Aspect | AssertionError | ValueError |
|---|---|---|
| Purpose | Debugging, testing assumptions | Handling invalid values |
| Can be disabled | Yes (with -O flag) |
No |
| Use in production | Not recommended | Yes |
| Example | assert age >= 0 |
raise ValueError("Age cannot be negative") |
BaseException
βββ SystemExit
βββ KeyboardInterrupt
βββ GeneratorExit
βββ Exception
βββ NameError β Undefined variable
βββ TypeError β Wrong type for operation
βββ ValueError β Wrong value (same type)
βββ ArithmeticError
β βββ ZeroDivisionError β Division by zero
βββ LookupError
β βββ IndexError
β βββ KeyError
βββ OSError
β βββ FileNotFoundError β File doesn't exist
β βββ PermissionError
β βββ ...
βββ RuntimeError β General purpose
βββ AssertionError β Assert statement fails
| Exception | Common Cause | Example |
|---|---|---|
NameError |
Undefined variable | print(x) where x not defined |
TypeError |
Operation on wrong type | "hello" + 5 |
ValueError |
Right type, wrong value | int("abc") |
ZeroDivisionError |
Division by zero | 10 / 0 |
FileNotFoundError |
File doesn't exist | open("missing.txt") |
OSError |
System-level error | Permission denied, disk full |
RuntimeError |
Unclassifiable error | Dictionary changed during iteration |
AssertionError |
Assert condition fails | assert x == 10 when x is 5 |
NameError: Python can't find a name you're trying to use. Check for typos, initialization, and scope.
TypeError: You're using the wrong type for an operation. Convert types or check with isinstance().
ValueError: The value is wrong for the operation (right type, wrong content). Validate values before use.
ZeroDivisionError: You tried to divide by zero. Always check denominators.
FileNotFoundError: The file you're trying to access doesn't exist. Check paths and file existence.
OSError: A system-level operation failed. Handle specific subclasses when possible.
RuntimeError: A catch-all for errors without a more specific class. Use specific exceptions when possible.
AssertionError: An assert statement failed. Use only for debugging and testing, not for production validation.
Exception Hierarchy: All exceptions inherit from Exception (which inherits from BaseException). Catch specific exceptions first.
Best Practice: Always handle exceptions as specifically as possible. Use except Exception: only as a last resort.
Test your understanding of the concepts covered in this tutorial. Answer each question, then click to reveal the correct answer.
Q1: Which exception is raised when you try to use a variable that hasn't been defined?
Q2: What exception would the following code raise?
result = "100" + 50
NameErrorTypeErrorValueErrorZeroDivisionErrorQ3: True or False: ValueError and TypeError are the same type of exception.
Q4: Which exception is a subclass of OSError?
RuntimeErrorValueErrorFileNotFoundErrorTypeErrorQ5: The following code raises a ValueError. Why?
number = int("12.5")
Q6: What is the difference between FileNotFoundError and OSError?
Q7: Which exception would be raised by the following code?
my_list = [1, 2, 3]
my_list.remove(5)
TypeErrorValueErrorNameErrorIndexErrorQ8: True or False: AssertionError can be disabled globally by running Python with the -O flag.
Q9: What exception is raised when you try to divide by zero?
ValueErrorTypeErrorZeroDivisionErrorRuntimeErrorQ10: Which of the following code snippets would correctly handle a FileNotFoundError?
try: open("file.txt") except: passtry: open("file.txt") except FileNotFoundError: print("File not found")try: open("file.txt") except OSError: print("Error")Complete the exercises below to reinforce your understanding. Sample solutions are provided after each exercise.
Instructions: For each code snippet, identify which exception will be raised and explain why.
a)
text = "Python"
print(text[10])
b)
def multiply(a, b):
return a * b
result = multiply("5", 3)
c)
import math
result = math.sqrt(-16)
d)
numbers = [10, 20, 30]
total = sum(numbers)
average = total / len(numbers)
print(average)
(Assume this runs without error. Then change numbers = [] and rerun.)
e)
my_dict = {"name": "Alice", "age": 25}
print(my_dict["city"])
f)
value = 42
value.append(5)
g)
def factorial(n):
if n < 0:
raise ValueError("n must be non-negative")
result = 1
for i in range(1, n + 1):
result *= i
return result
print(factorial(-5))
Instructions: Write a Python program that:
ValueError: If the user enters non-numeric input.ZeroDivisionError: If the user enters 0 for the denominator.TypeError: If something unexpected happens (safety net).def divide_numbers():
try:
num1 = float(input("Enter the first number: "))
num2 = float(input("Enter the second number: "))
result = num1 / num2
print(f"{num1} / {num2} = {result}")
except ValueError:
print("Error: Please enter valid numeric values.")
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
except TypeError:
print("Error: Unexpected type error occurred.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
# Run the function
divide_numbers()
Instructions: Write a function read_file_safe(filename) that:
FileNotFoundError: Prints "File not found: {filename}"PermissionError: Prints "Permission denied: {filename}"IsADirectoryError: Prints "Expected a file, but got a directory: {filename}"OSError: Prints "An OS error occurred: {error}"Exception: Prints "An unexpected error occurred: {error}"None if an error occurs.Test your function with: A non-existent file, a file you don't have permission to read, and a directory name.
def read_file_safe(filename):
try:
with open(filename, "r") as file:
return file.read()
except FileNotFoundError:
print(f"File not found: {filename}")
except PermissionError:
print(f"Permission denied: {filename}")
except IsADirectoryError:
print(f"Expected a file, but got a directory: {filename}")
except OSError as e:
print(f"An OS error occurred: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
return None
# Test the function
print(read_file_safe("non_existent.txt"))
# This would require setting up actual test files/directories
Instructions: The following code raises a RuntimeError. Identify the error and write proper code that avoids it or handles it gracefully.
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key in my_dict:
if key == 'b':
my_dict['d'] = 4
print(key, my_dict[key])
Problem: The error occurs because the dictionary is modified during iteration (adding a new key while iterating).
Fixed Version 1 β iterate over a copy of keys:
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key in list(my_dict.keys()): # Create a copy of keys
if key == 'b':
my_dict['d'] = 4
print(key, my_dict[key])
Fixed Version 2 β use exception handling:
my_dict = {'a': 1, 'b': 2, 'c': 3}
try:
for key in my_dict:
if key == 'b':
my_dict['d'] = 4
print(key, my_dict[key])
except RuntimeError as e:
print(f"Error: {e}")
print("Hint: Don't modify a dictionary while iterating over it.")
Instructions: Write a function validate_age(age) that:
assert to check that age is an integer.assert to check that age is between 0 and 150.AssertionError that occurs by printing "Invalid age" and returning None.Test the function with:
validate_age(25)validate_age(-5)validate_age(200)validate_age("twenty-five")def validate_age(age):
try:
assert isinstance(age, int), "Age must be an integer"
assert 0 <= age <= 150, "Age must be between 0 and 150"
return age
except AssertionError as e:
print(f"Invalid age: {e}")
return None
# Test the function
print(validate_age(25)) # Output: 25
print(validate_age(-5)) # Output: Invalid age: Age must be between 0 and 150, None
print(validate_age(200)) # Output: Invalid age: Age must be between 0 and 150, None
print(validate_age("25")) # Output: Invalid age: Age must be an integer, None
1. For each of the following scenarios, identify the most specific exception that would be raised and explain why. Then, write a try...except block that handles that specific exception.
a) You try to open a file for reading, but the file doesn't exist in the current directory.
Exception: FileNotFoundError
try:
with open("missing_file.txt", "r") as f:
content = f.read()
except FileNotFoundError:
print("The file could not be found.")
b) You try to add an integer and a string together.
Exception: TypeError
try:
result = "Hello" + 5
except TypeError:
print("Cannot add string and integer.")
c) You try to convert the string "hello" to an integer.
Exception: ValueError
try:
number = int("hello")
except ValueError:
print("Invalid literal for integer conversion.")
d) You try to access the 10th element of a list that has only 3 elements.
Exception: IndexError
my_list = [1, 2, 3]
try:
print(my_list[10])
except IndexError:
print("Index out of range.")
e) You try to divide a number by zero.
Exception: ZeroDivisionError
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero.")
2. The following code has poor exception handling. Rewrite it to follow best practices:
try:
filename = input("Enter filename: ")
file = open(filename, "r")
content = file.read()
number = int(content)
result = 100 / number
print(result)
file.close()
except:
print("An error occurred")
Requirements for your rewrite:
else and finally where appropriate.with statement for file handling.def process_file():
try:
filename = input("Enter filename: ")
try:
with open(filename, "r") as file:
content = file.read()
except FileNotFoundError:
print(f"Error: File '{filename}' not found. Please check the filename and path.")
return
except PermissionError:
print(f"Error: Permission denied to read '{filename}'.")
return
except IsADirectoryError:
print(f"Error: '{filename}' is a directory, not a file.")
return
except OSError as e:
print(f"Error: OS error while opening file: {e}")
return
try:
number = int(content.strip())
except ValueError:
print(f"Error: File content '{content.strip()}' is not a valid integer.")
return
try:
result = 100 / number
except ZeroDivisionError:
print("Error: File content was zero, cannot divide by zero.")
return
except TypeError:
print("Error: Unexpected type error during division.")
return
print(f"Result: {result}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
# Run the function
process_file()
3. Research the following exceptions and write a paragraph about each: RecursionError, KeyError, AttributeError, ImportError, StopIteration. For each exception, provide:
RecursionError:
def recursive(): return recursive() β RecursionError: maximum recursion depth exceededKeyError:
LookupError.my_dict = {"a": 1}; print(my_dict["b"]) β KeyError: 'b'dict.get(key, default) or try...except KeyError.AttributeError:
x = 42; x.append(5) β AttributeError: 'int' object has no attribute 'append'hasattr() or use try...except AttributeError.ImportError:
import non_existent_module β ModuleNotFoundError (subclass of ImportError)try...except ImportError for optional dependencies.StopIteration:
next() function.it = iter([1, 2]); next(it); next(it); next(it) β StopIterationfor loops. For manual iteration, use try...except StopIteration.4. Design a simple logging system that writes error messages to a file. The system should:
Define a function log_error(message, filename="error_log.txt") that:
FileNotFoundError (create the file if it doesn't exist).PermissionError (print to console instead).OSError (print a generic message to console).Write a test program that:
import datetime
def log_error(message, filename="error_log.txt"):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] {message}\n"
try:
with open(filename, "a") as log_file:
log_file.write(log_entry)
except FileNotFoundError:
# File doesn't exist, create it
with open(filename, "w") as log_file:
log_file.write(log_entry)
except PermissionError:
print(f"WARNING: Cannot write to log file ({filename}). Printing to console:")
print(log_entry.strip())
except OSError as e:
print(f"WARNING: OS error while writing to log file: {e}")
print(log_entry.strip())
except Exception as e:
print(f"WARNING: Unexpected error while writing to log: {e}")
def test_logging():
try:
# Test with a non-existent file
with open("missing.txt", "r"):
pass
except FileNotFoundError as e:
log_error(f"File not found: {e}")
try:
# Test division by zero
x = 10 / 0
except ZeroDivisionError as e:
log_error(f"Division by zero: {e}")
try:
# Test invalid conversion
x = int("abc")
except ValueError as e:
log_error(f"Value error: {e}")
print("All errors logged. Check error_log.txt")
if __name__ == "__main__":
test_logging()
This tutorial provides a comprehensive understanding of the most common built-in exceptions in Python. The quizzes, exercises, and homework problems will help reinforce the concepts and prepare you for real-world programming scenarios. Happy learning!