Python 3, an Illustrated Tour Transcripts
Chapter: Language syntax
Login or purchase this course to watch this video and the rest of the course contents.
0:00 In this video we're going to talk about exception chaining. This came out in pepped 3134.
0:06 There are a few new things that this introduces in Pythons exceptions. That's the __context__, __cause__ and the __traceback__.
0:13 We'll look at all of them here. The motivation for this, the pep states that during handling of one exception, exception a,
0:20 it may be possible that another exception, exception b, may occur. If this happens exception b is propagated outward and exception a is lost.
0:28 In order to debug the problem, it's useful to know about both exceptions, the __context__ attribute retains this information.
0:34 So let's look at an example here. I'm trying to divide 1 by 0, I'll get a 0 division error
0:40 that will raise an exception and I can inspect that exception here and note that I'm just printing the string of the exception,
0:47 the context of it, the __context__, the __cause__ and the __traceback__ and because this is the original exception here, I only have that exception,
0:55 there's no context no __context__ and no __cause__. There is a __traceback__ which has the __traceback__ for the exception.
1:00 Now, let's change it a little bit. Let's make a function called divide work that does some division
1:05 and if there's a 0 division error, it will call log, the log function. And in this case, let's pretend that log talks to a cloud-based logging provider
1:16 and for some reason this is down. So instead of actually logging it raises a system error that says logging is not up.
1:21 So now we're going to have a couple errors here if we divide by 0 we're going to try and log that
1:27 and we're going to get another error that says a system error. So if we look at what happens when we say divide 5 by 0,
1:33 it gives us a traceback and it says we got to 0 division error and it says during the handling of that 0 division error another exception occurred.
1:41 We also got this system error logging is not up. Let's try and call our divide work with a 0 division error and see what the exception looks like.
1:50 If we inspect the exception, we'll see that we got the logging is not up exception. So this means we're getting a 0 division error,
1:57 which is trying to log that and it's getting the logging is not up error. If we look at the __context__ there, there we see the 0 division error
2:04 and there is no __cause__ and we see that we have a traceback. So by having multiple exceptions here we can inspect the __context__
2:14 and see where that exception came from, in this case, the logging up exception came from having it as 0 division error.
2:23 Let's look at the motivation for __cause__ it says sometimes it can be useful for an exception handler
2:28 to intentionally reraise an exception either to provide extra information or to translate an exception to another type.
2:35 The __cause__ attribute provides an explicit way to record the direct cause of an exception
2:40 let's look at __cause__ here is some code that illustrates it we still have our divide work function, it's changed a little bit.
2:46 If we get a 0 division error, we're going to log that and in this case our log will not fail, it's just going to print that out
2:53 but we are going to raise another exception instead, we're going to raise it an arithmetic error,
2:57 and we're going to say raise that from the original exception. If we call it here, we can see that we get a 0 division error
3:04 and it says that the above exception was the direct cause of the following exception, the arithmetic error.
3:11 So the 0 division error caused reraised the arithmetic error from that 0 division error. And if we inspect the __cause__ attribute of the exception
3:21 the exception that we get is bad math and it was caused by the 0 division error,
3:27 note that the context is also the same error there with the same exception, but because we said raise this new exception from the original exception,
3:37 this is the original exception that we raised from the 0 division error. Let's look at the motivation for adding __traceback__.
3:45 It says adding the __traceback__ attribute to exception values makes all the exception information accessible from a single place.
3:52 Python 3 also added __traceback__ to the exception. The reason why they did this was just to make it nice to have around.
4:01 prior to Python 3, in order to get the traceback you had to import the sys module and try and pull the traceback off of that.
4:09 In Python 3, they're just going to give it to you so we can look at the traceback by just inspecting the __traceback__ attribute if we need to.
4:17 This might be useful for low-level logging or figuring out what your issues are, if you need to dig into them.
4:24 One thing to note is that because the exception contains the traceback and that can have variable state in Python 3, there is an explicit decision
4:31 to actually remove exception variables following the exception block. So here's the exception block in here and in Python 3
4:41 we have access to the e variable inside of that. In Python 2, the e variable sits around afterwards.
4:48 But in Python 3, if we try to inspect that e following our exception block, that indented block
4:55 we will not have it anymore, so this is cleaned up to not leak information. Just one thing to be aware of.
5:00 Let's look at some suggestions for exception handling. Mistake one suggestion is to make your own specific exceptions
5:06 and this just helps readability and discoverability rather than gripping through a code base with a lot of key error or index error.
5:14 If you have something that's specific to your code and is named specifically, it makes it easier to find and easier to debug.
5:22 Another suggestion is to be specific about what exceptions you handle. So if you've got to try statement don't just put any exception after it
5:30 be very specific about the exceptions you handle. A general rule of thumb in Python is we want to only handle exceptions
5:37 that we know that we can recover from and so these two sort of go hand-in-hand
5:41 if we can only recover from certain exceptions, just catch those exceptions. don't be general and catch any exception.
5:47 So to summarize, exceptions are made a little bit nicer in Python. You can raise exceptions from other ones.
5:54 You have the context of where the exception happened, and again in Python, we want to be very specific and only handle what we can
6:01 so we've got a couple suggestions for best practices for exception handling. Hopefully, this helps you be better
6:07 and make your code a little bit more clear and more robust to failures.