https://ift.tt/MmShJkF Python Exceptions — What, Why, and How? Understand how Exceptions work in Python and how to to use them properly ...
Python Exceptions — What, Why, and How?
Understand how Exceptions work in Python and how to to use them properly
What is an Exception?
Exceptions are like some irritating friends that you will meet one way or another in your Python journey. Any time you try to do something unruly or ask Python to do something that Python doesn’t like, you will likely be handed over with some Exceptions that will stop or just warn you depending on the level of “offense.”
It’s Python’s way to tell you that what you asked for is either not achievable or needs something to be taken care of to avoid future execution failure.
In this post, we will go into the details of Python Exceptions. We will learn how to handle them, understand them as Python classes, how to customize our responses based on the type of exceptions, and will also know how to create own own exceptions.
Since Exceptions are classes themselves, we will use a lot of references to Python classes. So in case you need a quick brush up, you can my blog post series on Object Oriented Programming in Python.
Handling Exceptions
Exceptions are Python’s way of telling you something didn’t go as planned or expected. In an interactive and ad-hoc coding scenario, such as data analysis, we typically don’t need to take care of exceptions since we fix the exceptions as we face them and move on. But if you want to put your script for any kind of automated task, such as an ETL job, leaving the exceptions unhandled is pretty much like leaving a loaded gun unattended and aimed at your feet. You never know when someone will give it a jolt and inadvertently shoot it!
By handling exception we basically mean setting up a set of commands so that if an Exception takes place, Python knows what to do next other than just throwing a fit.
The Simplest Way
To capture the exceptions we use a code block called try-except. We put the piece of code that is suspected to be a source of error inside a try block then capture and design the response inside the except block. Check the following example where function calcArea is defined to receive a value from the users for theradius of a circle and returns the calculated area of the circle.
Area for radius 5 = 78.53999999999999
Something is wrong with a
Area for radius 4 = 50.2656
Area for radius 8 = 201.0624
Something is wrong with b
Area for radius 0 = 0.0
In the for loop,
Python first executes the calcArea() inside the try block.
- It tries to calculate the area and save it to the variable called area.
- If it doesn’t encounter any exception, it prints out the newly created variable area.
- But if it encounters a problem, it moves to the except code block.
Inside except code block,
- The Exception class is captured or stored and given a name - e, or give it any name of your wish. This named object is then used later to access the elements of the exception class object.
But what’s the benefit of exception handling?
Notice, how the for loop didn't break once it got unexpected values i.e. strings. If we didn't handle the Exceptions thrown from calcArea(), the for loop would have broken right after executing the first element.
🛑 Give it a try by to see yourself! Try running calcArea() in the loop without using try-except.
Let’s be Slightly More Engaged
The try-except code block has two additional branches: else, and finally.
- else code block is executed only if no exception takes place. We can use it to print out a custom message for a successful operation in a cleaner way - other than cramping it inside the try block.
- finally code block is always executed irrespective of whether there is an exception or not. Commonly used to leave a footprint to flag the end of the operation.
calcArea() ran successfully for 5
Area for input 5 = 78.53999999999999
calcArea() run completed on 2022-03-24 10:36:49.778314
Something is wrong for a.
Area for input a = None
calcArea() run completed on 2022-03-24 10:36:49.778314
calcArea() ran successfully for 4
Area for input 4 = 50.2656
calcArea() run completed on 2022-03-24 10:36:49.779315
calcArea() ran successfully for 8
Area for input 8 = 201.0624
calcArea() run completed on 2022-03-24 10:36:49.779315
Something is wrong for b.
Area for input b = None
calcArea() run completed on 2022-03-24 10:36:49.779315
calcArea() ran successfully for 0
Area for input 0 = 0.0
calcArea() run completed on 2022-03-24 10:36:49.779315
How About Being More Expressive!
Our try-except block tells us there's something wrong but doesn't tell us what exactly went wrong. In this section, we will work on that.
As we already know that exceptions in Python are basically classes themselves, they also come with some built-in variables of a Python class. In the following example, we will use some of these class properties to make the error messages more expressive.
Let’s look at the examples first then we will come back to discuss more about the class properties used in the example.
calcArea() ran successfully for 5
Area for input 5 = 78.53999999999999
calcArea() run completed on 2022-03-24 10:36:52.476687
Area couldn't be calculated for a.
Error type: ValueError
Error msg: could not convert string to float: 'a'.
Area for input a = None
calcArea() run completed on 2022-03-24 10:36:52.476687
calcArea() ran successfully for 4
Area for input 4 = 50.2656
calcArea() run completed on 2022-03-24 10:36:52.476687
calcArea() ran successfully for 8
Area for input 8 = 201.0624
calcArea() run completed on 2022-03-24 10:36:52.476687
Area couldn't be calculated for b.
Error type: ValueError
Error msg: could not convert string to float: 'b'.
Area for input b = None
calcArea() run completed on 2022-03-24 10:36:52.477706
calcArea() ran successfully for 0
Area for input 0 = 0.0
calcArea() run completed on 2022-03-24 10:36:52.477706
- type(e).__name__: Prints out the subclass name of the Exception. except block prints out TypeError for when it encounters string as input.
- print(e): Prints out the entire error message.
But if e is an object of the Exception class, shouldn't we use something like e.print_something()?
It’s possible because the Python Exception classes come with a built-in method called __str__() in them. Having this method defined in a class makes it possible to directly print out a class.
🛑 Give it a try! Rather than calling e, call e.__str__() to print out the error message.
What if We Needed Some Customization?
From our calcArea() function, we can see that ValueError is a repeated error type—where users input a string rather than a numeric value and thus Python fails to perform a numeric operation. How about if we customize try-except block to be more accommodative to this common error type and give the users another chance before ignoring their input entirely?
To do that we can call out a separate code block specifically for the ValueError exception. Where we will check or validate the input type to ensure it's only int or float type. Otherwise, we will keep prompting the user to input a numeric value as input.
To make the validation process simpler, let’s define a function called validate_input() - it'll check, and return True if the input variable is a float type otherwise return False status.
def validate_input(value):
try:
value = float(value)
return True
except: return False
calcArea() ran successfully for 5
Area for input 5 = 78.53999999999999
calcArea() run completed on 2022-03-24 10:36:57.001287
Input data is not numeric type.
Please input a numeric value: 8
Area for input 8 = 201.0624
calcArea() run completed on 2022-03-24 10:37:00.426775
calcArea() ran successfully for 4
Area for input 4 = 50.2656
calcArea() run completed on 2022-03-24 10:37:00.426775
calcArea() ran successfully for 8
Area for input 8 = 201.0624
calcArea() run completed on 2022-03-24 10:37:00.426775
Input data is not numeric type.
Please input a numeric value: k
Please input a numeric value: 999
Area for input 999 = 3135319.9416
calcArea() run completed on 2022-03-24 10:37:04.927187
calcArea() ran successfully for 0
Area for input 0 = 0.0
calcArea() run completed on 2022-03-24 10:37:04.927187
To custom response for ValueError, we have added a dedicated code block to capture only ValueError exception. Inside that code exception block,
- We are printing a message to the users.
- Then we are requesting for a numeric input in a loop until the input type is numeric.
- And finally upon receiving a numeric type input we call calcArea() again.
Notice a couple of things,
📌 We have put the ValueError code block above the Exception block. If we did otherwise our code would never have reached the ValueError section. Since it's a subclass of the Exception class, it would have been captured by the Exception code block.
📌 We didn’t use ValueError as e in ValueError code block but we could have. Since we didn't need any class properties to print out or for other use, we didn't capture TypeError class as an object.
🛑 Think about a scenario where you may want to incorporate other specific exception types. How would you incorporate them?
Understanding Exceptions as a Class
I have mentioned several times in this post that Exceptions are classes but haven’t got much detail into that rather than showing some applications. Let’s try to do that in this section.
In Python, all the Exceptions derive from the class called BaseException.
Below BaseException all the built-in exceptions are arranged as a hierarchy. The main four subclasses under BaseException are:
- SystemExit
- KeyboardInterrupt
- GeneratorExit
- Exception
The exceptions that we commonly care about or want to take action upon are the ones under the Exception subclass. Exception subclass again contains several groups of subclasses. Following is a partial tree hierarchy of the Python classes. For detail refer to this official document from python.org.
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
| +-- ModuleNotFoundError
+-- LookupError
🛑 You can run methods used to check common class hierarchy relations on these classes to check the above hierarchy yourself. Try running,
- print(BaseException.__subclasses__()): you should see the main subclasses under the BaseException class.
- print(issubclass(ValueError, Exception)): you should see True as return value since ValueError is a subclass of Exception.
Implementing Our Own Exception
So far we have been using only the exceptions that are already defined by Python. What if we want to have a further customized exception? To do that we can define our own exception class.
Since Exceptions are class, we can write our own custom exception class the same way we write a regular Python class.
In our example, let’s assume that we want to restrict the user input to a manageable size so that a user can’t ask the program to calculate area for an arbitrarily large radius. For demonstration purposes, let’s say we want to restrict it within 50 inches.
We can do that easily by adding an if condition inside the calcArea() function. But there's a nicer way of doing it using exceptions.
Writing a Custom Exception
For our solution, we will first define our own exception class called Above50Error.
class Above50Error(Exception):
def __init__(self, value):
Exception.__init__(self)
self.value = value
def __str__(self):
return "Input {} is larger than 50 inches".format(self.value)
Above50Error exception class is created as a subclass of the built-in Exception class so that it inherits all the properties.
- We initiated it with one parameter: value which is then stored as a class variable called value.
- Then we overrode the __str__() method that's inherited from the Exception class to print out a custom message.
Note that to print out a custom message we could’ve just added a message as a parameter while initializing Above50Error and pass it to Exception initialization. Exception class would have taken the argument passed as a message and customize the __str__() method. But we implemented it the longer way only for demo purposes.
🛑 Give it a try! Modify Above50Error so that it doesn't need the __str__() method to print a message.
🛑 Also, can you think of a way to confirm if Above50Error is actually a subclass of Exception?
To learn more about Python class and subclass relation you can check out my post on Object Oriented Programming in Python — Inheritance and Subclass
Implementing a Custom Exception
Since Above50Error is a custom exception, we need to push Python to raise it as an exception when we want to register this as an exception. Thus when we put that code inside a try-except code block it can capture the behavior as an exception. To do that we use the raise keyword.
So we modified calcArea() function to add a condition that checks if the input value is higher than 50. If it is it raises Above50Error exception otherwise it moves on and calculates the areas.
def calcArea(radius):
pi = 3.1416
radius = float(radius)
if radius > 50:
raise Above50Error(radius)
else:
area = pi * radius ** 2
return area
calcArea() ran successfully for 5
Area for input 5 = 78.53999999999999
calcArea() run completed on 2022-03-24 10:37:15.619721
calcArea() ran successfully for 5
Area for input 5 = 78.53999999999999
calcArea() run completed on 2022-03-24 10:37:15.619721
Area couldn't be calculated for 55.
Error type: Above50Error
Error msg: Input 55.0 is larger than 50 inches.
Area for input 55 = None
calcArea() run completed on 2022-03-24 10:37:15.619721
calcArea() ran successfully for 4
Area for input 4 = 50.2656
calcArea() run completed on 2022-03-24 10:37:15.619721
calcArea() ran successfully for 8
Area for input 8 = 201.0624
calcArea() run completed on 2022-03-24 10:37:15.619721
Input data is not numeric type.
Please input a numeric value: k
Please input a numeric value: 7
Area for input 7 = 153.9384
calcArea() run completed on 2022-03-24 10:37:20.308639
calcArea() ran successfully for 0
Area for input 0 = 0.0
calcArea() run completed on 2022-03-24 10:37:20.308639
Notice how the except code block for Exception captures our customed exception. Before wrapping up let's highlight some features of a custom exception class:
- Custom exception classes are like regular built-in exception classes. For example, we could have created a separate except code block for Above50Error as we did for ValueError. Give it a try!
- Also, since these are classes we could create their own subclasses to have even more customization on error and exceptions.
What’s Next?
This will be a wrap on my Object-Oriented Programming in Python series. In this series I tried to explain:
- What is OOP and why you should care about it?
- Understanding a class.
- Understanding inheritance and application of this concept in the subclasses.
- Understanding Python variables in the class context.
- Understanding Python methods.
And finally, in this blog, we went deep into the Python exceptions as an example of Python class.
Thanks for reading the post and hopefully this blog series will give you a good initial understanding of Object Oriented Programming in Python. In a future series, we will go on another journey into the world of Python class and have a much deeper understanding.
Python Exceptions — What, Why, and How? was originally published in Towards Data Science on Medium, where people are continuing the conversation by highlighting and responding to this story.
from Towards Data Science - Medium https://ift.tt/BWxzopT
via RiYo Analytics
No comments