Exceptions in Python

Vivek Shrikhande
The Startup
Published in
11 min readJun 9, 2019

Overview

1 Introduction
2 Exceptions in python
— 2.1 Handling exceptions
— — 2.1.1 Basic try-except block
— — 2.1.2 Multiple except clauses
— — 2.1.3 Multiple exceptions in a single except clause
— — 2.1.4 Aliasing
— — 2.1.5 Else clause
— — 2.1.6 Finally clause
— 2.2 Raising exceptions
— — 2.2.1 Raising built-in exceptions
— — 2.2.2 Defining and raising custom exceptions
3 Logger.exception

1 Introduction

In any programming language, we see two broad types of errors viz., compilation errors and run-time errors. Compilation errors are found during compilation of source code and these occur as a result of incorrect syntax or semantics. For example,

print(‘hello there!!’

Here, we missed the closing parenthesis which will result into syntax error when tried to execute. This error is found while compiling that code; so it’s called compilation error (syntax error to be specific). To fix the error in the above line, we just need to put a closing parenthesis at the end i. e., we just need to correct the syntax/semantics like the following,

print(‘hello there!!’)

Run-time errors on the other hand occur while program is executing. These errors occur as a result of abnormal context/input [1], a piece of code is being executed in/for. That is, a syntactically and semantically correct program may or may not throw run-time errors depending on the context or input. For example,

res = 40 / 2

Above code is syntactically and semantically correct and doesn’t throw any error. res = 20 after executing. Now, see here

res = 40 / 0

Though the code is syntactically and semantically correct, throws error when executed. Because the result of division by zero is undefined. That’s an example for run-time error on abnormal input. These run-time errors are also called as exceptions.

Example for run-time error arising as a result of context, is running a program (that uses operating system specific functionalities) on an incompatible system.

Why run-time errors are bad?
These are bad because they terminate the program once they occur.

Here is a situation; we have developed a calculator program, say for basic arithmetic operations (+, -, * and /) on two numbers. The program will ask for two numbers and an operation to apply on them. Assume that the program is in loop i. e., once executed, it will ask for 3 inputs (2 operands and 1 operator) calculates and shows the result, wait for the next set of 3 inputs, calculates and shows result, then for the next set of inputs and so on.

The program will work all good until a division operation with divisor as zero comes (say 25 / 0). For this input, program raises a run-time error/exception as it cannot calculate the result and the program terminates immediately. You will have to restart the program to get going again every time such inputs come. That’s just a small calculator program taking a second or a couple to restart but, how about a software that takes minutes or hours of time to restart? Or where restarting is not at all feasible? Tremendously bad.

Then, is there a way to prevent these exceptions from happening?
No, we can’t prevent them from happening. But, we can handle them so that the program won’t get terminated.

Wait, what is handling an exception means?
It means that the occurred exception cannot terminate the program (since that exception is handled). Program continues it’s execution.

Then, how to handle exceptions?
This is discussed in subsequent sections.

Whatever we discussed till now is applicable to almost all programming languages that support exceptions and their handling. Now, let’s narrow our discussion down to a specific language — Python!! From this section till the end, everything is specific to Python language and may not be specified explicitly.

2 Exceptions in python

2.1 Handling exceptions

2.1.1 Basic try-except block

Exceptions are handled through try statement. Here is the basic syntax,

Code that is suspected to throw/raise exception goes under try clause of try statement. The code that should be executed once the raised exception is handled, should go under except clause. See the following example,

Output for different input,

>>> divide(50, 2)
Result = 25
>>> divide(50, 0)
Divisor is zero; Division is impossible

The try statement works as follows,

try-except flow diagram

2.1.2 Multiple except clauses

It is possible that your code may raise more than just one type of exception. It may be ValueError, AttributeError, KeyError etc. These are some of the built-in exceptions i.e., these are available right out of the box from Python. You can get a whole list of them here. You can create and raise exceptions of your own! We will talk more on that a little later.

But how do you handle multiple exceptions (both built-in and custom) coming from your code in try block? Is there any support for this in Python? Yes, there is. That is using multiple except clauses. Give a look here,

>>> int_value = int(a)

This statement may raise ValueError or TypeError or may not raise any exceptions at all depending upon the type and value of variable a. Suppose a = 3.2 or a = '1200' then no exception; if a = '12k' then ValueError and if a = [1, 2] then TypeError is raised.

Here is how we handle both of them,

It’s just a ladder of except clauses with exceptions that you intend to handle. One thing that should be taken care of is the order of handling.

Exception in except clause handles exception of the same type or types derived from it. But the reverse is not true i. e., exception in except clause does not handle exception of base type from which it is derived.

For example, the following code will print B, C, D in that order, (taken from official documentation)

Note that if the except clauses were reversed (with except B first), it would have printed B, B, B — the first matching except clause is triggered.

Easy way to visualize,

Here, Right hand side kid is the raised exception; Left hand side kids are exception handlers (i.e., present in except clause); D derives from C which derives from B

2.1.3 Multiple exceptions in a single except clause

There is a shorthand(not exactly) for the above syntax. Here, is its general syntax,

And here is an example,

That is, combine all the exceptions that you intend to handle into a tuple. The exceptions are matched in the order of their appearance in the tuple. The rule (2.1.2) applies here again. You can mix up both the approaches i.e., having a ladder of except clauses each handling more than one exception (put in tuple).

Finally, what is the difference between the above two approaches and which one fits better in a given situation?
The difference exists in how you want to respond when exceptions are handled.

  • If you want to execute the same piece of code for all exceptions being handled then use the second (2.1.3) approach. Because it avoids the repetition of the same code in multiple except clauses.
  • If you want to execute different piece of code depending on the exceptions being handled, then go for the first (2.1.2) approach. This can also be accomplished by the second (2.1.3) approach like this,

But, it’s better to use the first (2.1.2) approach for such scenarios as it is more understandable and uses support provided by the language itself rather than a workaround like the above code.

2.1.4 Aliasing

Notice the following line in the code above,

except (exception_1, exception_2, ..) as e:

That is the exception(s) is(are) being given a name. Technically, it is called aliasing.

With aliasing, we can access the exception(s) with a common name. This is useful if you want to use attributes of the exception being handled. Aliasing is done through as keyword. The syntax is here,

Where <alias> should be replaced with an alias name.

And an example is,

Output:

('Invalid name')

You can use any name for the alias (but definitely not the built-in keywords); e is just a popular one.

Note: If your aliasing a single exception then surrounding parentheses are optional. Parentheses are mandatory if your aliasing multiple exceptions as in the above example.

2.1.5 Else clause

Came across a situation where you wanted to execute a piece of code if and only if the code under try clause did not raise any exception?
If not, see here

Problem: Write a function ‘divide’ to divide ‘a’ by ‘b’ (sent as parameters). Print the following,

  • If b is 0, then print ‘Cannot divide by zero’
  • Otherwise print ‘Output = <quotient>’ where <quotient> is the result of division

Solution:

>>> divide(20, 10)
Output = 2.0
>>> divide(20, 0)
Cannot divide by zero

else clause is optional and if it is present, it should always follow except clause. Code under else clause is executed only when code under try clause raises no exception. If an exception occurs in the try block then else block won’t be executed though the except clause handles it.

2.1.6 Finally clause

The try statement has one last clause finally, which is basically used for clean up actions. When used, it should follow all other clauses. finally clause is executed in any event irrespective of whether an exception has occurred or not. finally clause does not strictly need the else or except clause to be present. Here is an example,

>>> divide(2, 1)
Output = 2.0
Executing finally clause
>>> divide(2, 0)
Cannot divide by zero
Executing finally clause
>>> divide("2", "1")
Executing finally clause
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

A finally clause is always executed before leaving the try statement, whether an exception has occurred or not. When an exception has occurred in the try clause and has not been handled by an except clause (or it has occurred in an except or else clause), it is re-raised (see the third call to divide function above) after the finally clause has been executed. The finally clause is also executed “on the way out” when any other clause of the try statement is left via a break, continue or return statement.

More real world use case of finally clause is for releasing resources such as files or database connections, etc.

What is the difference between having a code in finally clause and the same code outside and after the try statement? Or to be simple, what is the difference between the following two scenarios?

The difference comes only when exception occurs (in any of try, except or else clause) and is not handled. In this case

  • the first code would print 'Leaving the function' and re-raises the exception.
  • the second code would not print 'Leaving the function' and the exception is passed onto the outer code

To say it in a sentence, the difference between the two scenarios is that the code in the finally clause is executed even if an exception occurs (and not handled) but not the code following the try statement.

2.2 Raising exceptions

2.2.1 Raising built-in exceptions

Python provides a way to raise an exception manually. It’s through raise keyword.

>>> raise AssertionError('Asserted statement is incorrect')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise AssertionError('Asserted statement is incorrect')
AssertionError: Asserted statement is incorrect

raise keyword takes only one argument that is either an exception class (that derives from Exception class) or an exception instance. In the above example the argument is an exception instance with a string message. This string message (optional) when sent should describe the error.

If the argument is an exception class, then it’s constructor is called with no arguments like the following case,

>>> raise AssertionError
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise AssertionError
AssertionError

2.2.2 Defining and raising custom exceptions

Before stepping in; Why custom exceptions?
There are certain situations wherein the built-in exceptions cannot be used to describe the error occurred meaningfully.

Say for example, we have a simple function which calculates the number of units (of electricity) consumed between two given readings. If any of the readings are negative, function raises ValueError and suppose the calculated consumption units is negative, then we want to raise an exception (say NegativeConsumptionError). But, this exception is not built-in into Python. We are still free to choose ValueError (most appropriate among built-ins) to handle this too. If we choose to use ValueError for this error also, then there is no way for the code calling electricity_consumption to differentiate between 'Negative reading' and 'Negative consumption'.

Using built-in ValueError

Using a custom exception

Got it; Now, how to define custom exceptions?

To define a custom exception, you just have to define a class derived from Exception class or sub-classes of it. Here, is a simple example(taken from official documentation)

A common practice is to create a base class for exceptions defined by that module, and subclass that to create specific exception classes for different error conditions. By having a base exception defined for a module (and then sub-classing from it to define specific exceptions), you can easily handle all the exception coming from it using that base exception.

You can set attributes on custom exceptions which can be retrieved in the handlers. See here

Output:

2 # 3
Unknown operator

3 Logger.exception

In any real time code, we all use loggers. One reason is, “they ease our code debugging”. Loggers in python have a special support for exceptions. Hence, I think they deserve at least a dozen of lines here.

Its logger.exception(). See the following code,

Output of this is, full stack trace of the exception handled rather than just a text describing it.

Advantages,

  1. You need not to use alias for the exception unless you need it inside the except clause; the exception occurred is implicit to logger.exception().
  2. In addition to stack trace, logger.exception() will print a message (that you send as an argument to it) on top of the stack trace. So, the log output of above code piece will be,
ERROR: Exception while performing division — handled
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise ZeroDivisionError
ZeroDivisionError

Note:
1. logger.exception() logs a message with level ERROR.
2.
logger.exception() should only be called from an exception handler.

References

Footnotes

[1] There may be other reasons too. But, the one dictated is the most common.
[2] Though you can determine which error occurred using description message of the ValueError exception, it may not be a good idea. Moreover, example is to just show a use case of custom exceptions.

--

--