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 Exception from which they inherit their standard attributes. All the standard errors are listed in the Python manual’s Listing of Builtin Exceptions. The names of the Python exceptions are important since we use them when trying to handle the errors. We’ll illustrate what we mean momentarily.


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 ([statement]) is executed when the try statement is reached. If any exception is raised within the try block, then the code within the except block ([FailureStatement]) is executed. If no exceptions are raised within the 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.


Code More, Distract Less: Support Our Ad-Free Site

You might have noticed we removed ads from our site - we hope this enhances your learning experience. To help sustain this, please take a look at our Python Developer Kit and our comprehensive cheat sheets. Each purchase directly supports this site, ensuring we can continue to offer you quality, distraction-free tutorials.


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 [Exception] statement that will engage 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 [Exception] with the given error message within the string [ErrText].

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 UndefinedDerivative exception for the 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 UndefinedDerivative? That’s pretty cool, isn’t it! All it takes to raise your own exceptions is to create a short class with a seemingly pointless statement: the 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.


Code More, Distract Less: Support Our Ad-Free Site

You might have noticed we removed ads from our site - we hope this enhances your learning experience. To help sustain this, please take a look at our Python Developer Kit and our comprehensive cheat sheets. Each purchase directly supports this site, ensuring we can continue to offer you quality, distraction-free tutorials.



Python Exceptions Practice Problems

  1. When the open function cannot find a given file, it will raise a FileNotFoundError exception. Write a try except statement that will handle such an exception.     Solution

  2. 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 golden. If a ZeroDivisionError exception occurs, then save the result as the float nan.     Solution

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 OutsideThresholdError.     Solution


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")

  1. 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.