In the previous lesson, you learned that most custom exceptions you will make day-to-day will start and end with a great descriptive name and inherit from the built-in Exception.
However, if you're feeling adventurous, then you can also modify the make-up and behavior of your custom exception by adding attributes or changing the functionality of the exception. This is not different from any other Python class.
Note: User-defined exceptions are usually kept simple. The example about extending a custom exception that you'll read on the page is more about what you can do rather than what you usually will do. Feel free to skip it and move on.
Create a Variable in Custom Exception
In your custom exception, you might want to pass the state of a specific variable in your program forward to your custom exception so that you can then use it there:
class AgeError(Exception):
def __init__(self, age):
self.age = age
age = int(input("Age: "))
if age < 0:
raise AgeError(age)
But wait a moment; this didn't change anything at all! You were able to pass a message to your custom exceptions before and have it displayed in the output, just like it does now, e.g., when entering -1 as your input:
Traceback (most recent call last):
File "<input>", line 2, in <module>
raise AgeError(age)
AgeError: -1
That's right, nothing seems to have changed. However, your AgeError() object now has an attribute called self.age that points to whatever value the user input converted to an integer is. Only right now, you can't see that or do anything because raising the AgeError() terminates your program.
Tasks
If you're wondering why you were able to pass any message to your exceptions without defining __init__() before, and you could still display that message in the traceback, then here are some tasks for you:
- Take a moment and think about how this might be possible. Write down your thoughts in your notebook.
- Open your Python console and raise a new
Exception()object, but pass it a number instead of a message to the constructor. - Do the same thing, but pass it a number and a message instead.
- Is there a limit to the arguments you can pass?
- Read over the description of
BaseException(). What do you think is responsible for this behavior?
If you don't care or feel like stopping halfway through these tasks, then that's perfectly fine, and you can just keep reading forward. Deep dives can be fun when the time is right :)
Access New Program Data
So, how can you get access to your newly defined AgeError.age attribute? Time to catch your custom exception:
class AgeError(Exception):
def __init__(self, age):
self.age = age
age = int(input("Age: "))
try:
if age < 0:
raise AgeError(age)
except AgeError as ae:
print(f"Pssst... This might be a miracle. They say they're {ae.age} years old.")
By catching the custom exception and assigning the exception object to the variable ae, you avoid terminating your program and give yourself access to the attributes of your custom exception class.
However, you might have noticed that you can't pass a message to your constructor anymore. This is because you've overwritten the __init__() of the Exception() parent class with your own custom __init__(), which only takes one argument that you've defined as self.age.
Info: This is what you'd expect with object-oriented inheritance. Revisit the OOP section of this course if you are unsure about the concepts you're seeing in practice here.
Custom Message Functionality
To get your message functionality back, you could re-establish the option to pass a message by adding a second parameter to your class constructor. Alternatively, you could define a standard message for every instance of your AgeError() exception:
class AgeError(Exception):
def __init__(self, age):
self.age = age
self.message = f"You'll be born in {abs(self.age)} years."
age = int(input("Age: "))
try:
if age < 0:
raise AgeError(age)
except AgeError as ae:
print(ae.message)
In this code snippet, you've added another instance attribute called self.message to your AgeError() class. The message uses the self.age attribute to construct a string that gives some prophetic feedback to the user.
Moving Forward
Because of the way that the Exception() class is defined and how Python handles exception objects, you'll only see the argument you passed to your constructor when raising the exception. If you want to work with your custom attributes, you'll need to catch the exception to gain access to the exception object without terminating your script.
Now that you've gained some experience messing with the internals of a custom exception, you might be glad (or disappointed) to read again that, most of the time, your custom exceptions won't do that. As mentioned earlier, most custom exceptions won't go beyond defining a descriptive name.
At this point, you've learned a lot about exceptions in Python, even on a microscopic level of fiddling with its internals. Now it's time to take a step back and take a bird's eye view of exceptions.
How should you work with exceptions on a more general level? When should you raise one, when should you create a custom exception, and where should you define those? The next lessons will cover these topics to help you better understand how and when to use exceptions in Python.
Summary: How to Modify a Python Custom Exception
- Arguments passed to an exception object will be saved to its
argstuple and displayed in the traceback output - You can overwrite the
__init__()in your custom exception to define custom attributes and methods - It is best to keep custom exception objects as basic as possible