Exceptions in Python

1. In Python, there is a distinction between two kinds of errors:

  • Syntax errors (parsing errors), which occur when the parser comes across a statement that is incorrect. For example:
    Trying to execute the following line:

    will cause a SyntaxError, and result in the following (or similar) message being displayed in the console:

 

  • Exceptions, which occur even when a statement/expression is syntactically correct; these are the errors that are detected during execution when your code results in an error which is not uncoditionally fatal.  For example:
    Trying to execute the following line:


    will cause a ZeroDivisionError exception, and result in the following (or similar) message being displayed in the console:

    The last line of the error message actually tells you what happened. There are many different types of exceptions, such as ZeroDivisionError, NameError, TypeError, and many more; and this part of the message informs you of what type of exception has been raised. The preceding lines show you the context in which the exception has occured.

Python 3 defines 63 built-in exceptions, and all of them form a tree-shaped hierarchy, although the tree is a bit weird as its root is located on top.

Some of the built-in exceptions are more general (they include other exceptions) while others are completely concrete (they represent themselves only). We can say that the closer to the root an exception is located, the more general (abstract) it is. In turn, the exceptions located at the branches’ ends (we can call them leaves) are concrete.

Note:

ZeroDivisionError is a special case of more a general exception class named ArithmeticError;
ArithmeticError is a special case of a more general exception class named just Exception;
Exception is a special case of a more general class named BaseException;

We can describe it in the following way (note the direction of the arrows – they always point to the more general entity):

BaseException

Exception

ArithmeticError

ZeroDivisionError

 

2. You can “catch” and handle exceptions in Python by using the try-except block. So, if you have a suspicion that any particular snippet may raise an exception, you can write the code that will gracefully handle it, and will not interrupt the program. Look at the example:


The code above asks the user for input until they enter a valid integer number. If the user enters a value that cannot be converted to an int, the program will print Warning: the value entered is not a valid number. Try again..., and ask the user to enter a number again. What happens in such a case?

    • The program enters the while loop.
    • The try block/clause is executed. The user enters a wrong value, for example: hello!.
    • An exception occurs, and the rest of the try clause is skipped. The program jumps to the except block, executes it, and then continues running after the try-except block.

If the user enters a correct value and no exception occurs, the subsequent instructions in the try block are executed.

 

3. You can handle multiple exceptions in your code block. Look at the following examples:

You can use multiple except blocks within one try statement, and specify particular exception names. If one of the except branches is executed, the other branches will be skipped. Remember: you can specify a particular built-in exception only once. Also, don’t forget that the default (or generic) exception, that is the one with no name specified, should be placed at the bottom of the branch (use the more specific exceptions first, and the more general last).

You can also specify and handle multiple built-in exceptions within a single except clause:


 

4. Some of the most useful Python built-in exceptions are: ZeroDivisionError, ValueError, TypeError, AttributeError, and SyntaxError. One more exception that, in our opinion, deserves your attention is the KeyboardInterrupt exception, which is raised when the user hits the interrupt key (CTRL-C or Delete).

To learn more about the Python built-in exceptions, consult the official Python documentation.

 

5. Last but not least, you should remember about testing and debugging your code. Use such debugging techniques as print debugging; if possible – ask someone to read your code and help you to find bugs in it or to improve it; try to isolate the fragment of code that is problematic and susceptible to errors: test your functions by applying predictable argument values, and try to handle the situations when someone enters wrong values; comment out the parts of the code that obscure the issue. Finally, take breaks and come back to your code after some time with a fresh pair of eyes.

 

6. You cannot add more than one anonymous (unnamed) except branch after the named ones.

 

7. All the predefined Python exceptions form a hierarchy, i.e. some of them are more general (the one named BaseException is the most general one) while others are more or less concrete (e.g. IndexError is more concrete than LookupError).

You should avoid placing more general exceptions before more specific ones inside the same except branche sequence. For example, you can do this:

but don’t do that (unless you’re absolutely sure that you want some part of your code to be useless)

Remember:

    • the order of the branches matters!
    • don’t put more general exceptions before more concrete ones;
    • this will make the latter one unreachable and useless;
    • moreover, it will make your code messy and inconsistent;
    • Python won’t generate any error messages regarding this issue.

Good:

Wrong:

 

Example of bad use.

a

 

8. The Python statement raise ExceptionName can raise an exception on demand. The same statement, but lacking ExceptionName, can be used inside the try branch only, and raises the same exception which is currently being handled.

The instruction enables you to:

    • simulate raising actual exceptions (e.g., to test your handling strategy)
    • partially handle an exception and make another part of the code responsible for completing the handling (separation of concerns).

What happened? An error?
THE END.

The raise instruction may also be utilized in the following way (note the absence of the exception’s name):

There is one serious restriction: this kind of raise instruction may be used inside the except branch only; using it in any other context causes an error.

The instruction will immediately re-raise the same exception as currently handled.

I did it again!
I see!
THE END.

 

Without rise:

I did it again!
THE END.

 

9. The Python statement assert expression

    • evaluates the expression;
    • if the expression evaluates to True, or a non-zero numerical value, or a non-empty string, or any other value different than None, it won’t do anything else;
    • otherwise, it automatically and immediately raises an exception named AssertionError (in this case, we say that the assertion has failed)

Enter a number: 2
1.4142135623730951

Enter a number: -1
Traceback (most recent call last):
File "main.py", line 4, in <module>
assert x >= 0.0
AssertionError

 

10. The else: branch of the try statement is executed when there has been no exception during the execution of the try: block.

Everything went fine
0.5
Division failed
None

 

11. The finally: branch of the try statement is always executed.

Everything went fine
It's time to say goodbye
0.5
Division failed
It's time to say goodbye
None

 

12. The syntax except Exception_Name as an exception_object: lets you intercept an object carrying information about a pending exception.

invalid literal for int() with base 10: 'Hello!'
invalid literal for int() with base 10: 'Hello!'

 

13. A property named args it’s a tuple designed to gather all arguments passed to the class constructor. It is empty if the construct has been invoked without any arguments, or contains just one element when the constructor gets one argument (we don’t count the self argument here), and so on.

:

my exception : my exception
my exception

('my', 'exception') : ('my', 'exception')
('my', 'exception')

 

14. The exception classes can be extended to enrich them with new capabilities, or to adopt their traits to newly defined exceptions.

Division by zero
Division by zero
Original division by zero
My division by zero

 

Another example:

Pizza ready!
too much cheese : 110
no such pizza on the menu : mafia

 

Example.

The code outputs: success done.

 

 

Built-in exceptions

BaseException

Location: BaseException

The most general (abstract) of all Python exceptions – all other exceptions are included in this one; It can be said that the following two except branches are equivalent: except: and except BaseException:.

 

ArithmeticError

Location: BaseException ← Exception ← ArithmeticError

An abstract exception including all exceptions caused by arithmetic operations like zero division or an argument’s invalid domain.

 

AssertionError

Location: BaseException ← Exception ← AssertionError

Description: a concrete exception raised by the assert instruction when its argument evaluates to False, None, 0, or an empty string

 

LookupError

Location: BaseException ← Exception ← LookupError

An abstract exception including all exceptions caused by errors resulting from invalid references to different collections (lists, dictionaries, tuples, etc.)

 

IndexError

Location: BaseException ← Exception ← LookupError ← IndexError

A concrete exception raised when you try to access a non-existent sequence’s element (e.g., a list’s element)

 

KeyError

Location: BaseException ← Exception ← LookupError ← KeyError

A concrete exception raised when you try to access a collection’s non-existent element (e.g., a dictionary’s element)

 

KeyboardInterrupt

Location: BaseException ← KeyboardInterrupt

A concrete exception raised when the user uses a keyboard shortcut designed to terminate a program’s execution (Ctrl-C in most OSs); if handling this exception doesn’t lead to program termination, the program continues its execution.

Note: this exception is not derived from the Exception class. Run the program in IDLE.

 

MemoryError

Location: BaseException ← Exception ← MemoryError

A concrete exception raised when an operation cannot be completed due to a lack of free memory.

 

OverflowError

Location: BaseException ← Exception ← ArithmeticError ← OverflowError

A concrete exception raised when an operation produces a number too big to be successfully stored.

 

ImportError

Location: BaseException ← Exception ← StandardError ← ImportError

A concrete exception raised when an import operation fails.

 

 

Some useful exceptions

ZeroDivisionError

This appears when you try to force Python to perform any operation which provokes division in which the divider is zero, or is indistinguishable from zero. Note that there is more than one Python operator which may cause this exception to raise. Can you guess them all?

Yes, they are: /, //, and %.

ValueError

Expect this exception when you’re dealing with values which may be inappropriately used in some context. In general, this exception is raised when a function (like int() or float()) receives an argument of a proper type, but its value is unacceptable.

TypeError

This exception shows up when you try to apply a data whose type cannot be accepted in the current context. Look at the example:


You’re not allowed to use a float value as a list index (the same rule applies to tuples, too). TypeError is an adequate name to describe the problem, and an adequate exception to raise.

 

AttributeError

This exception arrives – among other occasions – when you try to activate a method which doesn’t exist in an item you’re dealing with. For example:


The third line of our example attempts to make use of a method which isn’t contained in the lists. This is the place where AttributeError is raised.

SyntaxError

This exception is raised when the control reaches a line of code which violates Python’s grammar. It may sound strange, but some errors of this kind cannot be identified without first running the code. This kind of behavior is typical of interpreted languages – the interpreter always works in a hurry and has no time to scan the whole source code. It is content with checking the code which is currently being run. An example of such a category of issues will be presented very soon.

It’s a bad idea to handle this exception in your programs. You should produce code that is free of syntax errors, instead of masking the faults you’ve caused.

IndexError

Look at the code in the editor. What will happen when you run it?

You will see the following message in reply:

Traceback (most recent call last):
File "lst.py", line 2, in
x = list[0]
IndexError: list index out of range

 

Exercise 1

What is the output of the following program if the user enters 0?


 

The program will output: Very bad input....

 

Ex. 2

Assuming the user enters 2 as input, what is the output of the following code snippet?

 

The code will raise a TypeError exception

  • The correct answer is TypeError. A TypeError exception is raised whenever an operation is performed on an incorrect data type. In our example, the input function assigns the value 2, entered by the user, into the variable x, as a string: x = '2'.
  • When evaluating the next print statement, the Python interpreter will raise a TypeError exception, since the division operator does not support string-type operands. In this case, the interpreter raises the TypeError exception before the ZeroDivisionError exception:print('2' / 0) -> TypeError .

 

Ex. 3

What is the output of the following code snippet?

 

Ex. 4

What is the output of the following code snippet?

Correct answer

A ValueError execption

 

Ex. 5

Which of the following code snippets raises a ValueError exception?

(Select two answers)


Correct selections:

 

Ex. 6

What is the output of the following code snippet?

Ppython

Python

A TypeError exception

pPython

 

Correct answer

A TypeError exception

 

Ex. 7

Which of the following code snippets causes a SyntaxError exception?

 

Ex. 8

What is the output of the following code snippet?


The code will raise the ValueError exception

The code will raise the AttributeError exception

The code will raise the TypeError exception

The code will raise the SyntaxError exception

 

Correct answer

A SyntaxError exception

 

Ex. 11

What is the output of the following code snippet?

A TypeError exception

None

7

3

 

Ex. 12

What is the expected output of the following code?

Let’s try to do this
We failed
We're done

 

Ex. 13

What is the expected output of the following code?

zero

 

Exercise 14

What is the expected output of the following code?

zero

Exercise 15

What is the expected output of the following code?

arith

Exercise 16

What is the expected output of the following code?

some

 

Exercise 17

What is the expected output of the following code snippet ?

The function my_root() attempts to return the result of root(x) (i.e. square root of x) and catches any exception – if there is an exception, message “Function Error !” is printed out and the same exception is raised again for processing outside of the function.

Line assert x + 2, "Wrong input !" will raise an AssertionError  if x+2  returns 0, along with the message “Wrong input !”.

With x=-2, the AssertionError  is raised and caught in the except: branch.

Consequently, the following is printed out :

Wrong input !

If x had been a positive integer, then function my_root()  would have returned the square root of x.

If x had been a negative integer (other than -2), then function my_root()  would have raised an exception (within the function and outside the function).

 

Exercise 18

What is the expected output of the following snippet ?

Function list_func() returns the result of the division of the 5th element (index 4) of the argument x by the last element (index -1) of the argument x.

list_func(my_list)  where my_list=[3,4,1,0]  will raise an exception because the list has only 4 elements. Division by zero won’t even be checked because the error occurs earlier.

This type of exception is an IndexError exception which is a subclass of LookupError exception.

So, the clause except IndexError:  in the function list_func()  will be executed and Function error #2 will be printed.

In addition, because of the raise command, the same IndexError exception is raised again – but this time the exception is handled outside of the function list_func(). The except LookupError: clause is the first one matching the IndexError exception and consequently Program Error #1 is printed.

So correct answer is :

Function Error #2

Program Error #1

 

Exercise 19

What is the expected output of the following code snippet ?

Can't divide by string !

Explanation:

AlphaDivisionError is a self-defined exception and is defined as a sub-class of the ZeroDivisionError class.

In function func_div(), if the second argument is a string (i.e. isinstance(y, str) returns True), the AlphaDivisionError exception is raised.

So :  print(func_div(4,'a')) will raise the AlphaDivisionError exception. The first  except:  branch that matches the AlphaDivisionError exception is the branch except ZeroDivisionError as e:  since AlphaDivisionError  is a sub-class of ZeroDivisionError.

Consequently, message Can't divide by string !  is printed out.