Python provides many built-in exceptions that are raised when certain errors occur during the execution of a program. Understanding these common exceptions, when they are raised, and how to handle them is an important part of writing robust Python code. This guide will provide an overview of some of the most frequently encountered built-in exceptions in Python.
Table of Contents
Open Table of Contents
Overview of Built-in Exceptions
All built-in exceptions in Python inherit from the base Exception
class. When an error occurs, Python will create an exception object containing information about the error and will raise it in the form of an exception.
Built-in exceptions can be grouped into several broad categories:
-
System/OS Errors - Raised for operating system-related errors like I/O failures or unavailable resources. E.g.
IOError
,OSError
,KeyboardInterrupt
. -
Value Errors - Occur when built-in operations or functions are applied to the wrong type of object. E.g.
TypeError
,ValueError
,IndexError
. -
Semantic Errors - Indicate issues with the meaning or logic of the code. E.g.
KeyError
,AttributeError
,AssertionError
. -
Syntax Errors - Raised when code is not syntactically correct. E.g.
IndentationError
,SyntaxError
. -
Warnings - Used to alert about deprecated features, poor coding practices, or probable bugs. E.g.
UserWarning
,DeprecationWarning
.
Below we will explore some of the most common built-in exceptions in more detail.
IOError and OSError
IOError
and OSError
are very common exceptions raised when an input/output operation fails, such as trying to open a file that doesn’t exist or trying to work with a closed file.
IOError
is used for I/O failures involving the Python built-in open()
function and streams. In Python 3, IOError
has been renamed to OSError
and handles all system-related errors.
For example:
try:
f = open('file.txt')
except IOError:
print("Could not open file.txt")
If ‘file.txt’ does not exist, an IOError
will be raised and the exception handler will print the error message.
OSError
is a broader category of exceptions that include things like missing files, full disks, permissions issues, damaged disks, etc.
For example:
import os
try:
os.remove('file.txt')
except OSError:
print("Error deleting file.txt")
If ‘file.txt’ is being used by another process, an OSError
may be raised.
To handle these exceptions, it’s good practice to catch both IOError
and OSError
since the latter subsumes the former in Python 3:
try:
open('file.txt')
except (IOError, OSError):
print("Error opening file!")
ValueError and TypeError
The ValueError
and TypeError
exceptions occur when you try to perform an operation on the wrong type of object.
ValueError
is raised when the type of object is expected, but the value is inappropriate.
For example:
import math
x = 'hello'
try:
print(math.sqrt(x))
except ValueError:
print("Cannot compute square root of string.")
Here a string is passed instead of a number, causing a ValueError
.
TypeError
is raised when the object type is not appropriate for the operation being attempted.
For example:
x = 5
try:
print(x + '10')
except TypeError:
print("Cannot concat integer and string.")
This raises a TypeError
because you can’t add an integer and string together using +
.
To avoid these errors, you should:
- Check types of function arguments
- Use type casting when appropriate
- Catch and handle
ValueError
andTypeError
Handling these exceptions gracefully allows your program to recover and continue execution when invalid values or object types are encountered.
AttributeError and NameError
The AttributeError
and NameError
exceptions are raised when code tries to access attributes or names that do not exist.
AttributeError
is raised when you try to access an attribute that is not defined on an object.
For example:
class Person:
pass
p = Person()
try:
p.name
except AttributeError:
print("Person object has no attribute 'name'")
Since Person
class did not define a name
attribute, referencing p.name
raises an AttributeError
.
NameError
occurs when a local or global name is not found. For example:
try:
print(some_unknown_var)
except NameError:
print("Name 'some_unknown_var' is not defined")
Trying to print a variable some_unknown_var
that is not defined raises a NameError
.
These exceptions can be avoided by:
- Defining attributes and methods on classes that need to be accessed
- Ensuring variables are initialized before use
- Using exception handling to catch attribute and name errors
KeyError and IndexError
KeyError
and IndexError
are raised when trying to access keys or indices on data structures like dictionaries and lists that do not exist.
KeyError
is raised when trying to access a dictionary key that does not exist.
For example:
d = {'name': 'John'}
try:
print(d['age'])
except KeyError:
print("Key 'age' not found in dict")
Since 'age'
is not a key in the d
dictionary, trying to access d['age']
raises a KeyError
.
IndexError
occurs when trying to access a list index that is out of bounds.
For example:
a = [1, 2, 3]
try:
print(a[3])
except IndexError:
print("List index out of range")
Here we try to print a[3]
but the valid indices are 0 through 2, so it raises an IndexError
.
These issues can be prevented by:
- First checking if a key is in a dict before accessing it
- Making sure a list index is less than the length of the list
- Including exception handling to deal with invalid indices/keys
AssertionError
An AssertionError
exception is raised when an assert
statement fails.
assert
is used to check if a condition is true and triggers an AssertionError
if it evaluates to False
.
For example:
x = 5
assert x == 5 # Passes
x = 10
assert x == 5 # Fails, raises AssertionError
Here the second assert
checks if x == 5
which evaluates to False
, so an AssertionError
is raised.
AssertionErrors
are typically handled by debugging the issue first. But you can catch and handle them as well:
try:
assert x == 5
except AssertionError:
print("Assertion failed!")
Using assert
allows you to sanity check expectations in your code. Mishandled assertions can potentially lead to bugs, so these exceptions should be addressed.
ImportError and ModuleNotFoundError
The ImportError
and ModuleNotFoundError
arise when there are issues importing a module.
ImportError
is the base exception for module/import related issues. For example:
try:
import somemodule
except ImportError:
print("Could not import somemodule")
This catches any generic failure to import a module.
ModuleNotFoundError
is new in Python 3 and is a subclass of ImportError
. It’s raised when a module is not found.
For example:
try:
import somemodule
except ModuleNotFoundError:
print("No module named 'somemodule' found")
This specifically catches when the module itself is unavailable.
Some reasons these exceptions occur:
- Typos in the module name
- An outdated module name
- The module not being installed
To fix import errors:
- Double check spelling of module names
- Try updating the module
- Install required modules
- Catch the exceptions and handle gracefully
Handling Multiple Exceptions
You can handle multiple exceptions using the same except
block:
try:
# Code that may raise exceptions
except (ValueError, TypeError):
# Handles ValueError and TypeError
You can also chain multiple except
blocks to handle specific exceptions differently:
try:
# Code
except ValueError:
# Handle ValueError
except TypeError:
# Handle TypeError
It’s good practice to order except
blocks from most specific to most general.
Raising Exceptions
In addition to built-in exceptions, you can raise your own exceptions using the raise
statement:
raise Exception('An error occurred')
After raising it, this exception can be caught and handled like any other exception:
try:
raise Exception('Error!')
except Exception as e:
print(e)
You can define custom exception classes that inherit from Exception
to provide added context.
For example:
class CustomError(Exception):
pass
raise CustomError("Custom error occurred")
Being able to raise your own exceptions is useful for signaling errors from your functions, classes, and modules.
Best Practices
Here are some best practices when dealing with exceptions in Python:
-
Avoid broad
except
clauses likeexcept:
that catch all exceptions. This can mask bugs and make issues hard to debug. -
Catch specific exceptions when possible instead of just the base
Exception
class to handle errors appropriately. -
Order
except
blocks from most specific to most general exceptions. -
Use finally blocks to ensure critical code gets run even if exceptions occur.
-
Document expected exceptions in docstrings and raise custom exceptions when needed.
-
Log exceptions to help debugging, but avoid exposing sensitive error information.
-
Raise exceptions at the source of the error rather than suppressing them.
Properly handling exceptions helps make Python programs more robust, user friendly, and secure. Mastering built-in exceptions is key to writing stable production-level code.
Conclusion
Python’s built-in exceptions serve an important role in signaling errors and anomalous conditions. Some of the most common exceptions include IOError
, ValueError
, KeyError
, AttributeError
, TypeError
, and ImportError
.
Knowing when these exceptions are raised and how to handle them properly ensures your Python programs are fault-tolerant and able to gracefully recover from exceptions. Exception handling is an essential skill for any Python developer.
This guide covered a variety of the core exceptions, when they occur, how to handle them through catching and raising exceptions. Ensure you leverage exception handling in your Python code to write stable and resilient programs.