- Introduction to Python Exceptions
- Python Exception Handling: try except Statements
- Raising Python Exceptions Manually
- Python Exceptions Practice Problems
Introduction to Python Exceptions
In our Getting Started with Python guide, we briefly introduced the concept of Python exceptions as being Python’s answer to runtime errors1. In this tutorial, we’ll focus more on how Python processes those exceptions, how to make your own exceptions, and how to handle Python exceptions to prevent your entire program from crashing.
What are Python Exceptions?
In Python, an exception is an error that we can manage, or handle. In earlier tutorials, we talked about a few different exceptions, like the following:
100/0 # Divide by zero
> ZeroDivisionError: division by zero
print(UndefinedVariable)
> NameError: name 'UndefinedVariable' is not defined
Exceptions are raised when Python encounters an action it cannot perform. After each of these exceptions were raised, the entire program aborted; the program ended whether it was running for 4 seconds or 4 days. This means that if we don’t want to ruin all of the work the program has performed, we’ll have to properly manage these exceptions.
Each of these Python exceptions can be avoided with careful planning and logic to avoid any illegal operation, however exceptions in Python can be handled in more relaxed ways. As we mentioned in the Getting Started with Python guide, Python has built in the philosophy of “better to ask for forgiveness than for permission” in the form of Python exception handling.
All the exceptions raised by Python are subclasses of the superclass
Python Exception Handling: try except Statements
Now that we understand the consequences of exceptions, let’s look at how we can handle them gracefully using Python’s native exception handling tools. The primary tool for exception handling with Python is the try except
context manager. This context manager has the following fundamental form:
try: [statement] except: [FailureStatement]
Like if
statements and for
loops, everything defined within the try
and except
indentations (tab or 4 spaces) will be included when those statements are executed.
Let’s look at each section in detail.
The try
statement invokes the context manager, and everything within the try
block (try
statement is reached. If any exception is raised within the try
block, then the code within the except
block (try
block, then the context manager is exited and the except
code block is skipped. You’ll only enter the except
block of a Python exception, or error, is raised.
For example:
a, b = 4, 2 # This will succeed
out = None
try:
out = a/b
except:
out = "Can't divide by 0!"
print(out)
> 2.0
In the above example, the try
statement code block was executed without raising any exception, therefore the except
code block was skipped and the variable out
has printed to the terminal as the correct quotient. Now let’s try a division that will fail:
a, b = 4, 0 # This will fail
out = None
try:
out = a/b
except:
out = "Can't divide by 0!"
print(out)
> Can't divide by 0!
Now, the try
code block raised an exception, and the except
code block was executed. The most important thing to notice in this example is that even though a ZeroDivisionError
was raised the program did not abort since it was tested within the try
context manager. This is extremely helpful for cases in which we know an error could occur in our program, and want to deal with the problem gracefully rather than have the program crash.
Get Our Python Developer Kit for Free
I put together a Python Developer Kit with over 100 pre-built Python scripts covering data structures, Pandas, NumPy, Seaborn, machine learning, file processing, web scraping and a whole lot more - and I want you to have it for free. Enter your email address below and I'll send a copy your way.
Proper Form: Narrowing try except Scope
In the previous section, we used the general form of a try except
statement that executed the except
code block whenever any exception was raised. Suppose we instead executed the following:
a = 4
out = None
try:
out = a/c # c was never defined
except:
out = "Can't divide by 0!"
print(out)
> 'Can't divide by 0!
In this case, we handled the exception in the case that the divisor was 0, however in this case the actual error that was raised was the NameError
arising from the undefined c
variable. Rather than allow the program to continue with the false assumption that it only raised a ZeroDivisionError
exception, we may want the program to abort so we can properly debug the code.
This situation is why it is considered good form to narrow the scope of the exceptions that are handled by the try except
statements. We narrow the scope by simply extending the format as follows:
try: [statement] except [Exception]: [FailureStatement]
Here we can define a tuple of valid exceptions within the except
statement. All other exceptions will abort the program unless they are handled within an outer-nested try
statement.
Let’s look at narrowing down some of our previous examples:
a, b = 4, 0
out = None
try:
out = a/b
except ZeroDivisionError:
out = "Can't divide by 0!"
print(out)
> 'Can't divide by 0!'
a = 4
out = None
try:
out = a/c # c was never defined
except ZeroDivisionError:
out = "Can't divide by 0!"
print(out)
> NameError: name 'c' is not defined
If we expect several different exceptions to arise within the try
statement code block, then we can add additional except
statements:
a, b = 4, 0
out = None
try:
out = a/b
except ZeroDivisionError:
out = "Can't divide by 0!"
except NameError: # Second except to check for NameError
out = "Can't find a variable!"
print(out)
> 'Can't divide by 0!'
out = None
try:
out = a/c
except ZeroDivisionError:
out = "Can't divide by 0!"
except NameError:
out = "Can't find a variable!"
print(out)
> 'Can't find a variable!'
Extending to try except else finally Statements
In the above examples, we used the short form of the try except
statement, however we can extend the full statement to the following for more robust Python exception handling and control:
try: [statement] except [Exception]: [FailureStatement] else: [ElseStatement] finally: [FinalStatement]
The else
statement block will only be executed if no exception is raised in the try
code block, and the finally
statement block will execute whether an exception is raised or not. The finally
statement can be especially useful during the execution of time-intensive code to perform clean-up actions in the event that the user stops the code execution with a keyboard interrupt.
Raising Python Exceptions Manually
When writing code, we may determine that there are some situations in which we will want to raise an exception manually. For example, suppose we wish to define a function dabs(x)
that is the derivative of the absolute value function abs(x)
. The derivative of the absolute value function is undefined at 0, therefore we would want to ensure that any input dabs(0)
will cause an error. We can raise an error ourselves using the raise [Exception]([ErrText])
statement format, which will raise the given
We define the dabs(x)
function as follows:
def dabs(x):
if x > 0:
return 1
elif x < 0:
return -1
else: # x = 0
raise ValueError("Derivative undefined at 0")
dabs(20)
> 1
dabs(0)
> ValueError: Derivative undefined at 0
Note that in the above raise statement, we used an already existing builtin exception ValueError
, which is why we didn’t have to define that keyword.
Creating New Exceptions
If the builtin Python exceptions are not adequate for the use of raising exceptions, we can create our own exceptions using classes. When we create these custom classes, we will inherit the standard exception attributes from the superclass Exception
. Because all the necessary methods and attributes are inherited from Exception
, we only have to include a pass
statement within the body of the class.
The pass
statement is fairly boring: it does absolutely nothing. When Python processes the pass
statement, it will simply continue executing the code as though it were not there. The usefulness of the pass
statement is in cases where Python expects some form of input, such as within defined functions and classes.
For example, we can define a new dabs(x)
function like this:
class UndefinedDerivative(Exception):
pass
def dabs(x):
if x > 0:
return 1
elif x < 0:
return -1
else: # x = 0
raise UndefinedDerivative("Derivative undefined at 0")
dabs(0)
> UndefinedDerivative: Derivative undefined at 0
You see how the name of the exception is now pass
statement.
To help you get more comfortable with Python exceptions and how to handle them, let’s take a look at a few practice problems. Before we do that, though, did you find this free tutorial helpful? Subscribe below and share this article with your friends, classmates, and coworkers on Facebook and Twitter! When you spread the word on social media, you’re helping us grow so we can continue to provide free tutorials like this one for years to come.
Get Our Python Developer Kit for Free
I put together a Python Developer Kit with over 100 pre-built Python scripts covering data structures, Pandas, NumPy, Seaborn, machine learning, file processing, web scraping and a whole lot more - and I want you to have it for free. Enter your email address below and I'll send a copy your way.
Python Exceptions Practice Problems
-
When the
open
function cannot find a given file, it will raise aFileNotFoundError
exception. Write atry except
statement that will handle such an exception. Solution -
Suppose you have the following list of numbers:
l = [[0,1],
[2,3],
[3,5],
[5,8],]
Write a Python code to divide each pair of numbers with the second number divided by the first (i.e. l[0][1]/l[0][0]
) and save the results in a list ZeroDivisionError
exception occurs, then save the result as the float
3. Write a program that allows a user to input two numbers, and print the result of the division of those numbers. If a ZeroDivisionError
exception occurs, alert the user and ask for two numbers again.
Solution
4. Write a program that allows a user to input one number. If the number is greater than 255, raise a ValueError
with an informative message.
Solution
5. Repeat problem 4, but instead of raising a ValueError
exception, write and raise a custom exception called
Solutions
1. Below is one such implementation.
file = "NotHere"
try:
with open(file, "r") as f:
pass #I/O Operations here
except FileNotFoundError:
message = "File " + file + " was not found."
print(message)
> File NotHere was not found.
2. Below is one such implementation.
l = [[0,1],
[2,3],
[3,5],
[5,8],]
golden = []
for i in l:
try:
golden.append(i[1]/i[0])
except ZeroDivisionError:
golden.append(float("nan"))
print(golden)
> [nan, 1.5, 1.666667, 1.6]
3. Below is one such implementation.
while True:
print("This program will return a/b")
a = float(input("Enter a: "))
b = float(input("Enter b: "))
try:
r = a/b
except ZeroDivisionError:
print("Sorry, cannot divide by 0. Try again.")
else:
print(r)
break
4. Below is one such implementation.
a = float(input("Enter a number: "))
if a > 255:
raise ValueError("Number greater than 255")
5. Below is one such implementation.
class OutsideThresholdError(Exception):
pass
a = float(input("Enter a number: "))
if a > 255:
raise OutsideThresholdError("Number greater than 255")
-
There also exists syntax errors that occur during interpretation of a section of code. These errors are typically not included under the umbrella of exceptions, and cannot be handled in the same manner. Syntax errors arise from improper coding, thus the correct way of handling them is to fix the code. ↩