def __exit__(self, *exc):
It’s time to address the big mystery. What in the world is the
*exc parameter in the
__exit__ method we have been writing so far?
Well, context managers play an important role in handling exceptions. Recall exceptions are errors that happen within the runtime of a code, terminating it before its completion. Within a context manager, the
__exit__ method is responsible for dealing with any exceptions. It can implement how to close the file and any other operations we want to perform if an exception occurs.
So far, we have been using
*exc to fill in the argument requirements for our context managers
__exit__ method. If we went back and wrote this instead:
We would have been met with a puzzling error:
__exit__() takes 1 positional argument but 4 were given.
This is because the
__exit__ method needs four total arguments! In the past exercises, we ignored this requirement by using the
* operator to tell the method we will pass a variable number of arguments even though we never did. It was a good way to put the above error on hold, but now let’s dive into what these required arguments are and how to use them so that we can master the
__exit__ method has three required arguments (in addition to
- An exception type: which indicates the class of exception (i.e.
- An exception value: the actual value of the error
- A traceback: a report detailing the sequence of steps that caused the error and all the details needed to fix the error.
Let’s take a look at an example context manager that deals with exceptions in its
class OpenFile: def __init__(self, file, mode): self.file = file self.mode = mode def __enter__(self): self.opened_file = open(self.file, self.mode) return self.opened_file def __exit__(self, exc_type, exc_val, traceback): print(exc_type) print(exc_val) print(traceback) self.opened_file.close()
__exit__ method, we are dealing with exceptions by adding a script that prints the exception values to the console. We can see the outcome of our simple exception handling when we run our
with statement with an intentional failure:
with OpenFile("file.txt", "r") as file: # .see() is not a real method print(file.see())
<class 'AttributeError'> '_io.TextIOWrapper' object has no attribute 'see' <traceback object at 0x7f08dcfb5040>
Traceback (most recent call last): File "script.py", line 14, in <module> print(file.see()) AttributeError: '_io.TextIOWrapper' object has no attribute 'see'
with statement is run, we get the above error message that tells us that we have an
AttributeError, that our object has no attribute
'see', and provides a traceback object. When an error occurs, the code stops, and resources (i.e., file in our earlier example) are still closed. The values of these three arguments are then thrown or suppressed.
In contrast, if no error occurs in the
with statement above, the
__exit__ method would have printed:
None None None
traceback are completely arbitrary names. We can use any name we want for these parameters as long as it does not hinder the readability of our code. In general, it’s best practice to be as descriptive as possible!
Now, let’s experience exceptions in context managers for ourselves.
Let’s return to our trusty
PoemFiles context manager. Unfortunately, it’s missing an
__exit__ method. Now that we have seen how to set up the method to capture exception data, let’s build it out.
__exit__ method, and add the 4 necessary arguments:
Traceback. Have the method use 3 different print statements to print each exception argument. This will help us visualize the exceptions when we run into them!
As the last part of our
__exit__ method, use the
.close() built-in function to close the
Looks like our context manager is complete. Time to see it in action!
Uncomment the first (marked
#First) commented out
with call that attempts to print the contents of our
poem.txt file in all uppercase.
Run the code and observe the exception data that comes up! Can you spot the error?
Looks like we ran into a small error in the last step! In particular, we ran into an
.uppercasewords() isn’t a real method.
Now let’s see what happens in our program when we don’t run into an error. Comment out the first
with statement we just ran and uncomment the second one (marked
Run the code and observe the exception data that comes up! In the next exercises, we’ll learn how to customize our exception handling to better work with errors that appear in our code.