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