Understanding Exceptions
00:00 In the previous lesson, I gave an overview of the course. In this lesson, I’ll be covering the exception class and how to use exception objects.
00:10
Exceptions interrupt the flow of code execution. They mostly get used for error handling, but Python also uses them for other things as well. When you iterate over an iterable using the for
keyword, a StopIteration
exception is what tells the for
loop that the iteration is complete.
00:30
There are also exceptions that are informational such as those used with the warn
function to inform programmers about deprecations. These don’t get raised like regular exceptions, but the classes themselves are still part of the exception hierarchy.
00:47 In programming, there are two general styles when dealing with error information for a line of code. “Look Before You Leap” means a piece of code, say a function call, returns a value indicating its success or failure.
01:00 After each line of code, it’s the programmer’s responsibility to check the error flag. It isn’t uncommon for the success indicator to just be ignored by the programmer and fingers get crossed hoping the code continues to work.
01:15 By contrast, the “Easier to Ask Forgiveness than Permission” style is what exceptions are for. You write a block of code, let the code run and if something goes wrong, the programming language invokes an associated error handler instead.
01:31 This way you can group multiple lines together and not have to worry about checking every single line at a time.
01:39
In Python, the “Easier to Ask Forgiveness” style is achieved through the use of a try/except
block set. If something goes wrong within the try
block the code raises an exception and Python transfers the code flow into the except
block.
01:55
Note that many languages use the terminology throw
and catch
for exceptions. Python instead uses raises
and handles
. As Python isn’t my first programming language, you might catch me using the wrong phrase.
02:10 See what I did there? I’m just gonna keep pointing out the bad dad jokes just in case they’re coming too fast for you. Enough blah to blah. Let’s write some code.
02:21 I’m inside my trusty REPL here. Let me start by creating a list,
02:30 not a fancy list, but it’s a list.
02:35
I access an item in the list using sequence notation. That’s those square brackets. Remember, Python is zero-indexed. names[2]
returns the third item in the list.
02:46 What happens if I try an index bigger than that,
02:50 and as you might have guessed, given the title of the course, you get an exception. Notice what this looks like. Let’s start at the bottom of the output and work our way backwards.
03:01
The highlighted IndexError
is the exception that got raised. Python’s a bit messy when it comes to exceptions. It names most of them errors. I suspect that’s to save typing, but the term exception is common in a lot of languages, so making it raising an error would’ve caused more confusion than it was worth.
03:20
So what you’ve got here is the state of things. Most exceptions are named error go figure. IndexError
is one of the exceptions built into the Python standard library.
03:31 It gets invoked in exactly this case when you use square brackets to reference something outside the size of a sequence. In this case, I’m asking for item index three.
03:42
That’s the fourth thing in a list with only three things in it. Next to the IndexError
is a message. The message is actually an argument to the IndexError
when it gets created and when you raise your own exceptions, you can set the contents of this message.
03:59
Its purpose is a bit like logging. It adds some more information so that the person seeing the exception has a better idea as to what caused it. Above the IndexError
is what’s called a traceback.
04:11 A traceback shows you what things got called in what order when the exception was raised. In this case, since I’m using the REPL, it only has one thing in it, a single line from standard input.
04:24 If I had a function that called a function that raised an exception, you’d get one line in the stack for each. A traceback gives you not only the exact place in your code where the exception was raised, but also who called the code to get to that state.
04:39
Alright, let’s add some error handling now. A try
block contains the code you want to run that might raise an exception.
04:49 Here I’m putting in the same problematic reference as before.
04:55
And then the except
block contains the handler. The code in this block is what gets run if the try
raises an exception. Spoiler alert, it will.
05:06
The except
keyword takes an optional argument, the kind of exception that this block is willing to handle. Here, I’ve used IndexError
.
05:15 If I hadn’t put it, the block would catch all exceptions. Because I do have it, if the code raises a different kind of error, the handler won’t get called.
05:29
Inside the except
block is where you put your error handling code. Often this means writing something more informative to the user, and for me in this case, I’ve printed something out.
05:41
Okay, let’s let this run. The end result is that final print statement. The call to names[3]
raises an IndexError
, gets caught by the handler, and then inside the handler, print
is called and that’s what writes your list doesn’t have that index to the screen. So exactly what happens when names[3]
gets run?
06:03
Well, Python underneath the covers is raising an IndexError
. There’s nothing magical about that. You can do it yourself.
06:13
The raise
keyword takes an instance of an exception class and then raises that exception. Note that because I didn’t include a message as an argument to the class, the bottom line here only shows the error itself.
06:26 Otherwise, the input pretty much looks just like the uncaught version above. Let me do it again, adding a message,
06:39
and there you go. Exceptions are a class hierarchy. The IndexError
exception, inherits from the exception exception
. Here I raised the general class itself this time including a message.
06:54 It’s not good practice to do this. You want to use an exception class that maps to the kind of thing that went wrong rather than using a generic one. And I’ll talk a little more about that in a future lesson.
07:11 Exceptions accept multiple arguments to the constructor. You’ve seen the message, but I can put more than that if I wish.
07:24 Except for the fact that you can raise them, exception objects are well just objects. Like I’ve done here, you can construct one and use it later. Since I only stored it away and I didn’t raise it, there’s no traceback here.
07:37 All I’m doing is holding onto it for now.
07:41
The .args
attribute of an exception contains the arguments passed into the constructor. This means you can get at the message or anything else the object was constructed with when you’re handling the exception.
07:53 I’ll cover more on this later as well. Since I’ve got it tucked away in a variable,
08:00 I can raise the variable. You don’t tend to do this often, but it can be handy if you want to do something like write a function that returns exception objects that you can raise later in case you’re trying to do something complicated.
08:14
Remember, everything in Python’s an object, even the exceptions. The try/except
block has one more optional part called finally
.
08:23
Sometimes you want some code to run even if an exception is raised. That’s what the finally
block is for. Say you opened a connection to the network and then something went wrong before you closed it.
08:35
You wanna make sure the closed method gets called. You do that in a finally
block.
08:49 I’m not going to muck around with network connections, but you can see from this code that the first print should run and the second shouldn’t.
08:58 Now I’m inside the exception handler
09:02
printing a little more info and this is a finally block. I’ll put print
in here as well.
09:13
Remember, this print
will get called whether or not there’s an exception. Let’s try this out, again with the dad jokes. This topic just kind of begs for it and there you go.
09:25 The first print gets called, the second doesn’t due to raising a type error. The third print is called inside the exception handler, and finally, the finally block is invoked.
09:35
That finally print would happen even if I hadn’t raised a TypeError in the try
.
09:44 You’ve already seen how you can add a message argument to an exception. Well, there’s a convention for the format of those messages. It should start with a lowercase letter and it should not finish with a period.
09:57 The world won’t end if you ignore this, but doing so keeps your messages consistent with how those coming from Python look and consistency’s good. Okay, that’s the exception object.
10:10 Let’s take a look at exceptions that come with Python.
Become a Member to join the conversation.