from IPython.core.display import HTML

HTML(open("custom.html", "r").read())

Optinal script

Exceptions in Python

The Python interpreter "throws exceptions" in case of errors. You have seen such exceptions already unless you never did a programming mistake until now ;)

Here we ask Python to divide by zero, which provokes an ZeroDivisionError:

def f(x):
    return 1 / x

def g(x):
    return f(x - 1)

g(1)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-2-49a8a393ca1e> in <module>()
      5     return f(x - 1)
      6 
----> 7 g(1)

<ipython-input-2-49a8a393ca1e> in g(x)
      3 
      4 def g(x):
----> 5     return f(x - 1)
      6 
      7 g(1)

<ipython-input-2-49a8a393ca1e> in f(x)
      1 def f(x):
----> 2     return 1 / x
      3 
      4 def g(x):
      5     return f(x - 1)

ZeroDivisionError: division by zero

If you read the long error message carefully (this is called a stack trace) you can see how the error was caused along the function calls.

Why exceptions ?

In languages having no exception mechanism (C, Fortran, ...) programers indicate error situations either using a global error variable or by using special return values. The drawbacks of these approaches are:

  • a global error variable must be checked always and will only handle error situations you are aware of
  • special return values are not always "available" depending on the expected "regular" return values and will be distinct from function to functions
  • conceptually an error and a return value are two different things which should not be mixed.

How to catch exceptions:

In the situation above the divison by zero caused stopping program execution. Here comes "exception handling" to the rescue: The try - except statements allow us to catch exceptions of a given class and handle them apropriately:

try:
    x = 1 / 0
except ZeroDivisionError as e:
    print("oops, I got '%s'" % e)

print("I reached the end of the program")
oops, I got 'division by zero'
I reached the end of the program

In the code above all ZeroDivisionError caused in the code block after try are caught and the code block after except is executed instead. In any case program execution continues after the try - except statements.

As you can see other exception classes are not caught in our code:

try:
    x = "1" * "2"
except ZeroDivisionError as e:
    print("oops, I got '%s'" % e)

print("I reached the end of the program")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-79dd04adfb2f> in <module>()
      1 try:
----> 2     x = "1" * "2"
      3 except ZeroDivisionError as e:
      4     print("oops, I got '%s'" % e)
      5 

TypeError: can't multiply sequence by non-int of type 'str'

To catch multiple exceptions we use the following syntax:

import random

try:
    if random.random() >= 0.5:
        x = "1" * "2"
    else:
        x = 1 / 0
except (ZeroDivisionError, TypeError) as e:
    print("oops, I got '%s'" % e)

print("I reached the end of the program")
oops, I got 'division by zero'
I reached the end of the program

In some situations we want to add an extra handling but then let the error handling process as usual. For this we can "reraise" the exception:

Exception hierarchy

The existing exceptions have an hierarchical order, see https://docs.python.org/3/library/exceptions.html#exception-hierarchy

So the "general" exception Exception subsumes ZeroDivisonError and TypeError. Using except Exception will catch both, and we can modify our excample like this:

import random

try:
    if random.random() >= 0.5:
        x = "1" * "2"
    else:
        x = 1 / 0
except Exception as e:
    print("oops, I got '%s'" % e)

print("I reached the end of the program")
oops, I got 'can't multiply sequence by non-int of type 'str''
I reached the end of the program

but this is dangerous ! It will catch your unintended programming mistakes as well !

So: take some time for proper exception handling, only catch the exceptions you are aware of, so for example catch the VisaIOError exception from pyvisa if needed.

How exceptions propagate

In case of nested functions an exception in an inner function will "bubble up". Either it is caught inbetween or the program execution will end. We can catch this exception on differnt levels, here we do this on the top level:

def f(x):
    x = 1 / x
    
def g(x):
    f(x)
    
def h(x):
    g(0)

try:
    h(0)
except ZeroDivisionError as e:
    print("got '%s'" % e)
got 'division by zero'

Raising your own exceptions

def f(x):
    if x < 0:
        raise ValueError("x is negative !")
    
f(-1)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-8-e64c7feebf61> in <module>()
      3         raise ValueError("x is negative !")
      4 
----> 5 f(-1)

<ipython-input-8-e64c7feebf61> in f(x)
      1 def f(x):
      2     if x < 0:
----> 3         raise ValueError("x is negative !")
      4 
      5 f(-1)

ValueError: x is negative !

Practical use of exceptions

"It's easier to ask forgiveness than it is to get permission" (https://en.wikiquote.org/wiki/Grace_Hopper#Quotes):

def is_proper_float(x):
    try:
        float(x)
        return True
    except ValueError:
        return False
    
while True:
    user_input = input("give me a real number: ")
    if is_proper_float(user_input):
        break
    print("I told you to enter a real number !")
print("well done !")
---------------------------------------------------------------------------
StdinNotImplementedError                  Traceback (most recent call last)
<ipython-input-9-79b461980848> in <module>()
      7 
      8 while True:
----> 9     user_input = input("give me a real number: ")
     10     if is_proper_float(user_input):
     11         break

/Users/uweschmitt/Projects/python3-course/venv/lib/python3.6/site-packages/ipykernel/kernelbase.py in raw_input(self, prompt)
    687         if not self._allow_stdin:
    688             raise StdinNotImplementedError(
--> 689                 "raw_input was called, but this frontend does not support input requests."
    690             )
    691         return self._input_request(str(prompt),

StdinNotImplementedError: raw_input was called, but this frontend does not support input requests.

Assertions

the assert statement raises an exception if the condition following the assert is false. We typeically use this for sanity checks and to implement type checks:

import math

def root(x):
    assert isinstance(x, int) or isinstance(x, float), "expected int or float, got %r" % x
    assert x >= 0, "expected non-negative x"
    
    return math.sqrt(x)

print(root("1"))  
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-10-24999925640c> in <module>()
      7     return math.sqrt(x)
      8 
----> 9 print(root("1"))

<ipython-input-10-24999925640c> in root(x)
      2 
      3 def root(x):
----> 4     assert isinstance(x, int) or isinstance(x, float), "expected int or float, got %r" % x
      5     assert x >= 0, "expected non-negative x"
      6 

AssertionError: expected int or float, got '1'

Exercises

  • repeat an play with the previous examples