Exception handling is your ability to customize and display error messages for parts of your program that fail to work.

Whether you're building a website, making an API, a module, or any other product using Python, your ability to effectively handle exceptions lets you explicitly state the cause of an error.

Here, we'll take a look at how you can handle exceptions in Python.

How Exception Handling Works in Python

When you raise exceptions, you're telling Python to bring up a message whenever a block of code fails. Exception handling is like telling someone to try and lift a weight. And if they can't, they should let you know.

To raise an exception in Python, however, you'll tell Python to try and run a particular block of code. If that block fails, you can then ask Python to raise a defined exception to the failed code.

When Should You Use Exceptions in Python Programming?

On most occasions, you can mask standard Python errors using exceptions. But you need to remain vigilant, as doing this may cause debugging issues. Consequently, you might find it hard to figure out the root cause of an eventual bug.

Therefore, you should use exceptions when you've sufficiently tested your code, and you're sure that it works. Ultimately, it's best practice to use them to handle potential faults that may arise from the user's end rather than the code itself.

In other words, you can use exceptions as a warning tool to guide users on how to use your program.

Handling Python Exceptions

To handle exceptions in Python, you first need to wrap your code in a try...except block. Occasionally, you might need to include a finally statement to handle further actions, depending on your needs.

The coding concept of Python exceptions generally looks like this:

        try:
    "code to be executed"
except:
    "error message"

As mentioned earlier, you can also use finally in an exception block. But the code you write inside a finally clause is independent and runs whether there's an exception or not.

In essence, it comes in handy if you have another block of code that you want to run continuously regardless of what happens within the try...except block.

Here's an example:

        try:
    print(9+6)
except:
    print("error message")
finally:
    print("please restart")

<strong>Output:</strong>
15
please restart

In the above code, please restart runs continuously, regardless of whether there's an exception or not.

An else condition can also follow an except statement:

        try:
    C = 2 + B
except:
    print("B needs to be defined")
else:
    print(u"Added successfully! The result is %s"%(C))

<strong>Output:</strong> B needs to be defined

Now try that again with "B" defined:

        try:
    B = 5
    C = 2 + B
except:
    print("B needs to be defined")
else:
    print(u"Added successfully! The result is %s"%(C))

<strong>Output:</strong> Added successfully! The result is 7

The above examples are unstandardized exceptions. But you can have a more explicit exception when you combine built-in (defined) exceptions with unstandardized ones:

        try:
    C = 2 + B
except NameError as err:
    print(err, ":", "B needs to be defined, please")
else:
    print(u"Added successfully! The result is %s"%(C))

<strong>Output:</strong> name 'B' is not defined : B needs to be defined, please

The above exception first checks if there's a NameError in the try block. It then prints the standard NameError exception first (" name 'B' is not defined"). And supports it with your written exception ("B needs to be defined, please").

Related: Basic Programming Principles Every Programmer Must Know

And if you want to handle a chain of exceptions, you can also accompany a try block with many except statements. This is pretty handy if your try block has the potential to have many exceptions:

        try:
    B = 5
    C = 2 + B
    D = float(6)
    F = 7/0

except NameError as err:
    print(err,":", "B needs to be defined, please")
except ValueError as val:
    print(val,":", "You can't convert that data")
except ZeroDivisionError as zeroerr:
    print(zeroerr,":", "You can't divide a number by zero")
else:
    print(u"Operation successfull! The results are: %s, %s, and %s"%(C, D, F))

<strong>Output:</strong> division by zero : You can't divide a number by zero

What if the division is valid? For instance, replacing F = 7/0 in the above code with F = 7/5 gives:

        <strong>Output:</strong> Operation successfull! The results are: 7, 6.0, and 1.4

User-Defined Exceptions in Python

You can come up with your exception as well and call them later in your program. This lets you give a specific description of your exception and name it as you like.

Nonetheless, every user-defined exception (directly or indirectly) still comes from the built-in Exception class of Python.

The example code below makes reference to the base Exception directly by calling RuntimeError from it:

        class connectionError(RuntimeError):
   def __init__(self, value):
      self.value = value
try:
   raise connectionError("Bad hostname")
except connectionError as err:
   print(err.value)

<strong>Output:</strong> Bad hostname

Note that connectionError, in this case, is a user-defined class, which you can raise anytime you need it in your program.

Related: The Beginner’s Guide to Regular Expressions With Python

You can make a user-defined exception by deriving it directly from the Exception base class. The exception below, however, prevents the subtraction of 5 from 6 and calls the exception from the base class directly:

        class errors(Exception):
    pass

class sixFiveError(errors):
   def __init__(self, value, message):
      self.value = value
      self.message = message
try:
   raise sixFiveError(6-5,"This substraction is not allowed")
except sixFiveError as e:
   print("There was an error:", e.message)

<strong>Output:</strong> There was an error: This substraction is not allowed

In practice, you can use an exception you defined earlier by calling it in another function. For instance, you can create a floatError that only allows the addition of two floats:

        # First call the base exception classes:

class errors(Exception):
    pass

# Next, derive your own exception from the base class:

class FloatError(errors):
   def __init__(self, value, message):
      self.value = value
      self.message = message

# Create a function to add two floats:
def addTwoFloat(a, b):
if (type(a) and type(b)) != float:
raise FloatError(a+b,"Numbers must be float to add")
else:
print(a + b)

addTwoFloat(4, 7)

<strong>Output:</strong> __main__.FloatError: (11, 'Numbers must be float to add')

Because you've now defined a FloatError class, Python raises it if you try to add two non-float literals using the addtwoFloat function.

You can print the FloatError class in the same Python file where you've created it to see what happens:

        print(FloatError)
<strong>Output:</strong> <class '__main__.FloatError'>

FloatError, however, isn't a built-in Python exception. You can verify this by calling FloatError in another fresh Python file where you've not created this class:

        print(FloatError)
<strong>Output:</strong> NameError: name 'FloatError' is not defined

You get a NameError because Python doesn't recognize it as a standard exception.

You can try self-defining other error classes as well to see how they play out.

Make Your Python Programs More User-Friendly With Exceptions

There are many standard exceptions in Python. But you can define yours as well. Nonetheless, the ease of using your program depends to some extent on how it handles various exceptions (whether user-defined, non-specific, or standard).

Exceptions, however, let you dictate how your program should work when users interact with them. Clearly and concisely stating the cause of an error also gives users a heads up about what they're doing wrong, and sometimes, it points them in the right direction.