When code executes, errors can occur. For example, if you could have invalid syntax:

print("cat)


The string cat is missing an ending quotation mark, leading to this error:

SyntaxError: EOL while scanning string literal


Here’s the error you get when you try to divide a number by zero:

ZeroDivisionError: division by zero


You sometimes might want to raise an error yourself. For example, suppose you have a function that doesn’t accept the input of 1, you could write something like this:

def f(x):
if x == 1:
raise ValueError("x cannot be 1")
print(x)


To raise an error yourself, use the raise keyword, followed by the name of the error you want to raise (in this case, ValueError) and pass in it the error message you want to provide. Thus, when you execute something like the following:

f(1)

print("Program continued!")


We get:

ValueError: x cannot be 1


Although error message like these are useful, one disadvantage is that they halt the execution of your code (notice how the print statement below did not execute). What if this error was not really that critical, and you wanted to proceed with the rest of your code? This is where the concept of try-except comes in:

try:
f(1)
except ValueError:
print("Function errored, but let's continue!")

print("Program continued!")


This gets us now:

Function errored, but let's continue!
Program continued!


Unlike before, we now get to the final print statement because we handled the error raised through this except statement and did not halt the program.

The purpose of the try-except format is to give us the possibility of guarding against potential errors that could halt our program. If no such errors occur, the code executes as normal. For example, if we had tried:

try:
f(2)
except ValueError:
print("Function errored, but let's continue!")

print("Program continued!")


Passing in 2 to f does not raise an error, so this gets us:

2
Program continued!


When we have a try-except statement, we can have multiple except blocks for a single try, just like we could have multiple elif blocks for a single if statement. Python checks each except block from top to bottom, checking to see if the error being handled matches the one that is raised. For example:

try:
f(2)
except TypeError:
print("Function errored strangely, but let's continue anyhow!")
except ValueError:
print("Function errored, but let's continue!")

print("Program continued!")


Note that if the error does not match to any of the ones provided, the error is raised and halts the program as before:

try:
f(2)
except TypeError:
print("Function errored, but let's continue!")

print("Program continued!")


We get the same error message as before and abort before getting to the last print statement, as the error is a ValueError, not TypeError:

ValueError: x cannot be 1


Finally, it should be noted that if we just have a bare except (i.e. one without any error name after it), it is considered a “catch-all” and can be used to handle any error raised.

try:
f(2)
except TypeError:
print("Function errored, but let's continue!")
except:
print("Catch all saves the day!")

print("Program continued!")


This will output:

Catch all saves the day!
Program continued!


Although the name Exception might have a negative connotation, exceptions are very useful in terms of adding more sophistication to the code that you write by providing a true mechanism for handling errors or invalid inputs provided.

That’s it for now! Let’s answer questions to review what we learned:

• How would you raise an OSError with the message: “Sorry about that! Something went wrong” ? (solution).

• Create a function that accepts one argument. Have the function print "Good input!", unless the argument is "cat", in which case, raise a ValueError (solution).

• Create a try-except block with your function by calling it with the argument "cat" in the try block. In the except block, have it print anything that you want. After the try-except, print something else (solution).

By the way, you may have noticed that error types are in fact classes, and their __init__ functions almost always accept an optional parameter for the error message that is displayed to users.

Finally, although this lesson presents only a few errors, we have seen several others throughout, including NameError (Lesson 3) and IndexError (Lesson 6).

If you are comfortable with errors, feel free to move on to imports.