Exceptions
Anyone who has written in any programming language for any amount of time has struck an exception. An exception is a message from the program that something has gone wrong.
Exceptions can be about all sorts of things - perhaps you tried to divide by zero, or misspelled a variable name and tried to access something that didn’t exist, or passed a string to a function when it was expecting a number.
Exceptions are raised - or thrown - by a program. The ones you might’ve seen before - like NameError
, ZeroDivisionError
, and IndexError
- are all built-in exceptions that are thrown by Python.
You can also throw custom exceptions in your programs. To do this, use the raise
keyword.
def half_even_number(n):
if (n % 2 == 0):
return n/2
else:
raise Exception(“This Function only supports halving even numbers. Received: {}”.format(n))
In this example, you raise an exception with a message - a string displayed to the user when the exception occurs. In this case, the exception provides information about the error itself.
It’s worth noting that all Python exceptions are objects. Exception
is the base class for Python exceptions that everything inherits from. It means you can make your own exceptions - which we’ll cover after a quick note.
Handling Exceptions
When an exception is raised in your code - and it isn’t handled - your program usually stops.
You can handle an exception using a try-except (often known in other languages as a try-catch statement).
def percent_increase(initial_value, after_value):
try:
return (after_value/initial_value)*100
except ZeroDivisionError:
return 0
except Exception as e:
print(“Uh oh, unexpected error occurred!”)
raise e
The function, percent_increase
, calculates the percentage increase (or decrease!) from an initial value. The calculation is in a try
block, which means that if an exception occurs, it’ll be handled by “except” blocks instead of crashing.
Let’s look at an example. The most obvious problem that could occur here is a division by zero error. If a ZeroDivisionError
is thrown in this function, you catch it and return 0 instead, thanks to the first exception block. The exception is handled, and the program doesn’t crash!
On the other hand, if the exception is not a ZeroDivisionError, you re-raise it. You’ll notice the as e
in the second except block; this assigns the exception object to the variable e, which you can then raise again - either to be handled by another try block in which this function was called or just to terminate the program.
When an exception is raised, it propagates through the program. Python no longer does things in order and instead keeps throwing the exception up the stack - that is, the pile of functions/methods that called other functions/methods. The exception will keep traveling upwards until it is handled or has nowhere else to go, at which point the program crashes.
Write Custom Exceptions
In a complicated program, many things can go wrong. To manage unique problems, Python allows you to define your own custom exceptions. Remember that exceptions are objects; you can define your exception classes just like you would any other.
class InvalidEmailException(Exception):
pass
class EmailContactSystem(ContactSystem):
def __init__(self, email_address):
if (not validate_email(email_address)):
raise InvalidEmailException(“Invalid email address: {}”.format(email_address)
self.email_address = email_address
Here is the EmailContactSystem
from before. In the constructor, call the validate_email function with the email address string that’s provided. If the email address fails validation, raise an InvalidEmailException
- which will likely need to be handled by whoever calls this constructor.
But InvalidEmailException
doesn’t do anything, so why use it?
A lot of custom exceptions look like this. While it’s possible to override methods in a custom exception, you get quite a lot of usefulness out of just having a separate type.
Remember the except statement - except InvalidEmailException
. The except
block matches on the type of the exception. Which is to say, that except
block will happen if the exception thrown is of type InvalidEmailException
. Just the type of the exception is enough!
One last thing: custom exceptions in a large application don’t usually directly inherit from exception. You usually do something more like this:
class MyAppException(Exception):
pass
class MyAppInvalidEmailException(Exception):
pass
MyAppException is the base class for the custom exception hierarchy. This allows you to quickly know if an exception is raised from your application or not. You’ll find that many Python libraries will have their own custom exception base class, too.
Your Turn: Use Custom Exceptions
Context:
In this final exercise, you'll wrap up by creating your own custom exceptions.
Instructions:
Given the following User class and its constructor, create two custom exceptions with a shared parent class. They should indicate a username that’s too short or an insufficient password.
Once you have your custom exceptions, modify the constructor below to check that usernames are at least three characters long and that passwords contain at least one letter and one digit. Raise the appropriate exception when there’s a problem.
Then, try creating a user with different usernames and passwords to test that it works.
class User:
def __init__(self, username, password):
self.username = username
self.password = password
Let’s Recap!
An exception is raised (or thrown) when a problem occurs in a program.
You can handle exceptions using try blocks.
Exceptions are just objects, which means you can define your own custom ones!
You're done! But before you go, test your knowledge with our Part 3 quiz.