Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Exception handling in Python

Exception handling

Hierarchy of calls

main()
    some_process()
        for filename in some_list:
            handle_file(filename)
                private_module.deal_with_file(filename)
                    private_module._helper_function(filename)
                       public_module.process_file(filename)
                           with open(filename) as fh:
                               pass

Handling errors as return values

  • Each function that fails returns some error indicator. None ? An object that has and attribute "error"?
  • None would be bad as that cannot indicate different errors.
  • Every called needs to check if the function returned error. If at any point we forget our system might run with hidden failures.
def some_function()
    result = do_something(filename)
    if result:
        do_something_else(result)
    else:
        return result

main()
    ...
    result = some_function()

  • If we forget to check the result and pass it on, we might get some error in the code that is quite far from where the error actually happened
main()
    ...
    result = do_something(filename)
    ...
    ...
    do_something_else(result)
  • This can happen even if we don't pass the result around:
main()
    ...
    do_something(filename)
    ...
    ...
    do_something_else_assuming_the_other_worked()

Handling errors as exceptions

  • Only need to explicitly check for it at the level where we know what to do with the problem.
  • But: Do we want our pacemaker to stop totally after missing one beat? Probably not. Or better yet: not when it is in production.
main()
    ...
    try:
        ...
        result = do_something(filename)
        do_something_else(result)
    except Exception:
        decide_what_to_do()

    always_happens()

A simple exception

  • ZeroDivisionError

When something goes wrong, Python throws (raises) an exception. For example, trying to divide a number by 0 won't work. If the exception is not handled, it will end the execution.

In some programming languages we use the expression "throwing an exception" in other languages the expression is "raising an exception". I use the two expressions interchangeably.

In the next simple example, Python will print the string before the division, then it will throw an exception, printing it to the standard error that is the screen by default. Then the script stops working and the string "after" is not printed.

def div(a, b):
    print("before")
    print(a/b)
    print("after")

div(1, 0)

# before
# Traceback (most recent call last):
#   File "examples/exceptions/divide_by_zero.py", line 6, in <module>
#     div(1, 0)
#   File "examples/exceptions/divide_by_zero.py", line 3, in div
#     print(a/b)
#           ~^~
# ZeroDivisionError: division by zero

Prevention

We might try to prevent the exceptions generated by the system, but even if succeed in preventing it, how do we indicate that there was an issue? For example with the input?

def div(a, b):
    if b == 0:
        # raise Exception("Cannot divide by zero")
        print("Cannot divide by zero")
        return None

    print("before")
    print(a/b)
    print("after")

div(1, 0)


Working on a list

In a slightly more interesting example we have a list of values. We would like to divide a number by each one of the values.

As you can see one of the values is 0 which will generate and exception.

The loop will finish early.

def div(a, b):
    print("dividing {} by {} is {}".format(a, b, a/b))

total = 100
values = [2, 5, 0, 4]

for val in values:
    div(total, val)

# dividing 100 by 2 is 50.0
# dividing 100 by 5 is 20.0
# Traceback (most recent call last):
# ...
# ZeroDivisionError: division by zero

We can't repair the case where the code tries to divide by 0, but it would be nice if we could get the rest of the results as well.

Catch ZeroDivisionError exception

  • except
  • ZeroDivisionError

For that, we'll wrap the critical part of the code in a "try" block. After the "try" block we need to provide a list of exception that are caught by this try-block.

You could say something like "Try this code and let all the exceptions propagate, except of the ones I listed".

As we saw in the previous example, the specific error is called ZeroDivisionError.

If the specified exception occurs within the try: block, instead of the script ending, only the try block end and the except: block is executed.

import sys

def div(a, b):
    print("dividing {} by {} is {}".format(a, b, a/b))

total = 100
values = [2, 5, 0, 4]

for val in values:
    try:
        div(total, val)
    except ZeroDivisionError:
        print("Cannot divide by 0", file=sys.stderr)

# dividing 100 by 2 is 50.0
# dividing 100 by 5 is 20.0
# Cannot divide by 0
# dividing 100 by 4 is 25.0

Module to open files and calculate something

Of course in the previous example, it would be probably much easier if we just checked if the number was 0, before trying to divide with it. There are many other cases when this is not possible. For example it is impossible to check if open a file will succeed, without actually trying to open the file.

In this example we open the file, read the first line which is a number and use that for division.

When the open() fails, Python throws an FileNotFoundError exception.

def read_and_divide(filename):
    print("before " + filename)
    with open(filename, 'r') as fh:
        number = int(fh.readline())
        print(100 / number)
    print("after  " + filename)

File for exception handling example

If we have a list of files and we would like to make sure we process as many as possible without any problem caused in the middle, we can catch the exception.

We have the following list of files. Notice that "two.txt" is missing and "zero.txt" has a 0 in it.

0
1

File two.txt is missing on purpose.

3

Open files - exception

import sys
import module

files = sys.argv[1:]

for filename in files:
    module.read_and_divide(filename)

# before one.txt
# 100.0
# after  one.txt
# before zero.txt
# Traceback (most recent call last):
# ...
# ZeroDivisionError: division by zero
python open_list_of_files.py one.txt zero.txt two.txt three.txt

Handle divide by zero exception

  • try
  • except
  • ZeroDivisionError

Running this code will the ZeroDivisionError exception, but it will die with a FileNotFoundError exception.

import sys
import module

files = sys.argv[1:]

for filename in files:
    try:
        module.read_and_divide(filename)
    except ZeroDivisionError:
        print(f"Cannot divide by 0 in file '{filename}'", file=sys.stderr)
    print('')

# before one.txt
# 100.0
# after  one.txt

# before zero.txt
# Cannot divide by 0 in file 'zero.txt'

# before two.txt
# FileNotFoundError: [Errno 2] No such file or directory: 'two.txt'
python handle_divide_by_zero.py one.txt zero.txt two.txt three.txt

Handle files - exception

  • FileNotFoundError

We can add multiple "except" statement at the end of the "try" block and handle several exceptions. Each one in a different way.

import sys
import module

files = sys.argv[1:]

for filename in files:
    try:
        module.read_and_divide(filename)
    except ZeroDivisionError:
        print(f"Cannot divide by 0 in file '{filename}'", file=sys.stderr)
    except FileNotFoundError:
        print(f"Cannot open file '{filename}'", file=sys.stderr)
    print('')

# before one.txt
# 100.0
# after  one.txt

# before zero.txt
# Cannot divide by 0 in file 'zero.txt'

# before two.txt
# Cannot open file 'two.txt'

# before three.txt
# 33.333333333333336
# after  three.txt
python handle_both_exceptions.py one.txt zero.txt two.txt three.txt

Catch all the exceptions and show their type

  • Exception

We can also use the "except Exception" to catch all exceptions. In this case we might want to also print out the text and the type of the exception by ourselves.

import sys
import module

files = sys.argv[1:]

for filename in files:
    try:
        module.read_and_divide(filename)
    except Exception as err:
        print(f"  There was a problem in '{filename}'", file=sys.stderr)
        print(f"  Text: {err}", file=sys.stderr)
        print(f"  Name: {type(err).__name__}", file=sys.stderr)
    print('')

# before one.txt
# 100.0
# after  one.txt

# before zero.txt
#   There was a problem in 'zero.txt'
#   Text: division by zero
#   Name: ZeroDivisionError

# before two.txt
#   There was a problem in 'two.txt'
#   Text: [Errno 2] No such file or directory: 'two.txt'
#   Name: FileNotFoundError

# before three.txt
# 33.333333333333336
# after  three.txt
python show_exception_type.py one.txt zero.txt two.txt three.txt

List exception types

We can list more than one exceptions to be caught one after the other in a single "except" statement.

except (ZeroDivisionError, FileNotFoundError):
import sys
import module

files = sys.argv[1:]

for filename in files:
    try:
        module.read_and_divide(filename)
    except (ZeroDivisionError, FileNotFoundError) as err:
        print(f"We have a problem with file '{filename}'", file=sys.stderr)
        print(f"Exception type {err.__class__.__name__}", file=sys.stderr)
    print('')

# before one.txt
# 100.0
# after  one.txt

# before zero.txt
# We have a problem with file 'zero.txt'
# Exception type ZeroDivisionError

# before two.txt
# We have a problem with file 'two.txt'
# Exception type FileNotFoundError

# before three.txt
# 33.333333333333336
# after  three.txt

python handle_list_of_exceptions.py one.txt zero.txt two.txt three.txt

Hierarchy of Exceptions

There are many kinds of exceptions in Python and each module can define its own exception types as well. On this page you'll find the list and hierarchy of exceptions in Python.

Order of exception handling - bad

import sys
import module

files = sys.argv[1:]

for filename in files:
    try:
        module.read_and_divide(filename)
    except Exception as err:
        print(f"General error {err}")
        print(f"Error class: {err.__class__.__name__}")
    except ZeroDivisionError:
        print("ZeroDivisionError")
        print(f"Cannot divide by 0 in file '{filename}'")
    print('')

# before one.txt
# 100.0
# after  one.txt

# before zero.txt
# General error division by zero
# Error class: ZeroDivisionError

# before two.txt
# General error [Errno 2] No such file or directory: 'two.txt'
# Error class: FileNotFoundError

# before three.txt
# 33.333333333333336
# after  three.txt

  • Both exception are caught by the first except entry

Order of exception handling - good

import sys
import module

files = sys.argv[1:]

for filename in files:
    try:
        module.read_and_divide(filename)
    except ZeroDivisionError:
        print("ZeroDivisionError")
        print(f"Cannot divide by 0 in file '{filename}'")
    except Exception as err:
        print(f"General error {err}")
        print(f"Error class: {err.__class__.__name__}")
    print('')

# before one.txt
# 100.0
# after  one.txt

# before zero.txt
# ZeroDivisionError
# Cannot divide by 0 in file 'zero.txt'

# before two.txt
# General error [Errno 2] No such file or directory: 'two.txt'
# Error class: FileNotFoundError

# before three.txt
# 33.333333333333336
# after  three.txt

  • Always try to handle the more specific exceptions first

How to raise an exception

  • raise
  • throw
  • Exception

As you create more and more complex applications you'll reach a point where you write a function, probably in a module, that needs to report some error condition. You can raise an exception in a simple way.

def add_material(name, amount):
    if amount <= 0:
        raise Exception(f"Amount of {name} must be positive. {amount} was given.")
    print(f"Adding {name}: {amount}")

def main():
    things_to_add = (
        ("apple", 3),
        ("sugar", -1),
        ("banana", 2),
    )

    for name, amount in things_to_add:
        try:
            add_material(name, amount)
        except Exception as err:
            print(f"Exception: {err}")
            print("Type: " + type(err).__name__)

main()

$ python raise.py 
Adding apple: 3
Exception: Amount of sugar must be positive. -1 was given.
Type: Exception
Adding banana: 2

Raise ValueError exception

  • ValueError

You can be more specific with your error type and raise a ValueError.

def add_material(name, amount):
    if amount <= 0:
        raise ValueError(f"Amount of {name} must be positive. {amount} was given.")
    print(f"Adding {name}: {amount}")

def main():
    things_to_add = (
        ("apple", 3),
        ("sugar", -1),
        ("banana", 2),
    )

    for name, amount in things_to_add:
        try:
            add_material(name, amount)
        except Exception as err:
            print(f"Exception: {err}")
            print("Type: " + type(err).__name__)

main()

$ python raise_value_error.py
Adding apple: 3
Exception: Amount of sugar must be positive. -1 was given.
Type: ValueError
Adding banana: 2

Stack trace of exceptions

import traceback

def bar():
    foo()

def foo():
    raise Exception("hi")

def main():
    try:
        bar()
    except Exception as err:
        track = traceback.format_exc()
        print("The caught:\n")
        print(track)

    print("---------------------")
    print("The original:\n")
    bar()


main()
The caught:

Traceback (most recent call last):
  File "stack_trace.py", line 11, in main
    bar()
  File "stack_trace.py", line 4, in bar
    foo()
  File "stack_trace.py", line 7, in foo
    raise Exception("hi")
Exception: hi

---------------------
The original:

Traceback (most recent call last):
  File "stack_trace.py", line 20, in <module>
    main()
  File "stack_trace.py", line 17, in main
    bar()
  File "stack_trace.py", line 4, in bar
    foo()
  File "stack_trace.py", line 7, in foo
    raise Exception("hi")
Exception: hi

No need for exception to print Stack trace

  • traceback
  • format_stack
import traceback

def foo():
  bar()

def bar():
    #print(traceback.extract_stack())
    print(''.join(traceback.format_stack()))

foo()
print("done")

#   File "python/examples/other/print_stack_trace.py", line 10, in <module>
#     foo()
#   File "python/examples/other/print_stack_trace.py", line 4, in foo
#     bar()
#   File "python/examples/other/print_stack_trace.py", line 8, in bar
#     print(''.join(traceback.format_stack()))
#
# done

Raise Exception from

  • from
  • None
  • raise
import sys

def lower():
    #print("starting lower")
    raise Exception("exception in lower")
    print("still here")

def upper(set_from):
    try:
        lower()
    except Exception as err:
        if set_from == "Default":
            raise Exception("from upper")
        elif set_from == "None":
            raise Exception("from upper") from None
        elif set_from == "Same":
            raise Exception("from upper") from err
        else:                      
            raise Exception("from upper") from Exception("Incorrect input")


def main():
    if len(sys.argv) != 2:
        exit("Usage: raise_from.py [Default|None|Same]")

    param = sys.argv[1]

    # try:
    #     upper(param)
    # except Exception as err:
    #     print("err:", err)
    #     print("cause:", err.__cause__)
    #     print("context:", err.__context__)

    upper(param)

main()

Exercise: Exception int conversion

  • In the earlier example we learned how to handle both ZeroDivisionError and FileNotFoundError exceptions. Now try this
cd examples/exceptions
python handle_both_exceptions.py one.txt zero.txt two.txt text.txt three.txt
before one.txt
100.0
after  one.txt

before zero.txt
Cannot divide by 0 in file 'zero.txt'

before two.txt
Cannot open file 'two.txt'

before text.txt
Traceback (most recent call last):
  File "handle_both_exceptions.py", line 9, in <module>
    module.read_and_divide(filename)
  File "/home/gabor/work/slides/python/examples/exceptions/module.py", line 4, in read_and_divide
    number = int(fh.readline())
ValueError: invalid literal for int() with base 10: '3.14\n'
  • This will raise a ValueError exception before handling file three.txt
  • Fix it by capturing the specific exception.
  • Fix by capturing "all other exceptions".
3.14

Exercise: Raise Exception

  • Write a function that expects a positive integer as its single parameter.
  • Raise exception if the parameter is not a number.
  • Raise a different exception if the parameter is not positive.
  • Raise a different exception if the parameter is not whole number.

Solution: Exception int conversion (specific)

import sys
import module

files = sys.argv[1:]

for filename in files:
    try:
        module.read_and_divide(filename)
    except ZeroDivisionError:
        print(f"Cannot divide by 0 in file '{filename}'")
    except FileNotFoundError:
        print(f"Cannot open file '{filename}'")
    except ValueError as err:
        print(f"ValueError {err} in file '{filename}'")


before one.txt
100.0
after  one.txt
before zero.txt
Cannot divide by 0 in file zero.txt
before two.txt
Cannot open file two.txt
before text.txt
ValueError invalid literal for int() with base 10: '3.14\n' in file text.txt
before three.txt
33.333333333333336
after  three.txt
python handle_3_exceptions.py one.txt zero.txt two.txt three.txt

Solution: Exception int conversion (all other)

import sys
import module

# python handle_both_exceptions.py one.txt zero.txt two.txt three.txt
files = sys.argv[1:]

for filename in files:
    try:
        module.read_and_divide(filename)
    except ZeroDivisionError:
        print("Cannot divide by 0 in file {}".format(filename))
    except IOError:
        print("Cannot open file {}".format(filename))
    except Exception as ex:
        print("Exception type {} {} in file {}".format(type(ex).__name__, ex, filename))


before one.txt
100.0
after  one.txt
before zero.txt
Cannot divide by 0 in file zero.txt
before two.txt
Cannot open file two.txt
before text.txt
Exception type ValueError invalid literal for int() with base 10: '3.14\n' in file text.txt
before three.txt
33.333333333333336
after  three.txt

Solution: Raise Exception

def positive(num):
   if type(num).__name__ == 'float':
       raise Exception("The given parameter {} is a float and not an int.".format(num))

   if type(num).__name__ != 'int':
       raise Exception("The given parameter {} is of type {} and not int.".format(num, type(num).__name__))

   if num < 0:
       raise Exception("The given number {} is not positive.".format(num))

for val in [14, 24.3, "hi", -10]:
   print(val)
   print(type(val).__name__)
   try:
       positive(val)
   except Exception as ex:
       print("Exception: {}".format(ex))

Advanced Exception handling

Exceptions else

  • else

  • The else part will be execute after each successful "try". (So when there was no exception.)

import sys
import module

# python else.py one.txt zero.txt two.txt three.txt
files = sys.argv[1:]

for filename in files:
    try:
        module.read_and_divide(filename)
    except ZeroDivisionError as err:
        print("Exception {} of type {} in file {}".format(err, type(err).__name__, filename))
    else:
        print("In else part after trying file {} and succeeding".format(filename))
        # Will run only if there was no exception.
    print()

Output:

before one.txt
100.0
after  one.txt
In else part after trying file one.txt and succeeding

before zero.txt
Exception division by zero of type ZeroDivisionError in file zero.txt

before two.txt
Traceback (most recent call last):
  File "else.py", line 9, in <module>
    module.read_and_divide(filename)
  File "/home/gabor/work/slides/python-programming/examples/exceptions/module.py", line 3, in read_and_divide
    with open(filename, 'r') as fh:
FileNotFoundError: [Errno 2] No such file or directory: 'two.txt'

Exceptions finally

  • finally

  • We can add a "finally" section to the end of the "try" - "except" construct.

  • The code in this block will be executed after every time we enter the try.

  • When we finish it successfully. When we catch an exception. (In this case a ZeroDivisionError exception in file zero.txt" %}

  • Even when we don't catch an exception. Before the exception propagates up in the call stack, we still see the "finally" section executed.

import sys
import module

# python finally.py one.txt zero.txt two.txt three.txt
files = sys.argv[1:]

for filename in files:
    try:
        module.read_and_divide(filename)
    except ZeroDivisionError as err:
        print("Exception {} of type {} in file {}".format(err, type(err).__name__, filename))
    finally:
        print("In finally after trying file {}".format(filename))
    print('')

Output:

before one.txt
100.0
after  one.txt
In finally after trying file one.txt

before zero.txt
Exception division by zero of type ZeroDivisionError in file zero.txt
In finally after trying file zero.txt

before two.txt
In finally after trying file two.txt
Traceback (most recent call last):
  File "finally.py", line 9, in <module>
    module.read_and_divide(filename)
  File "/home/gabor/work/slides/python-programming/examples/exceptions/module.py", line 3, in read_and_divide
    with open(filename, 'r') as fh:
FileNotFoundError: [Errno 2] No such file or directory: 'two.txt'

Exit and finally

  • finally

The "finally" part will be called even if we call "return" or "exit" in the "try" block.


def f():
    try:
        return
    finally:
       print("finally in f")

def g():
    try:
        exit()
    finally:
       print("finally in g")

print("before")
f()
print("after f")
g()
print("after g")

# before
# finally in f
# after f
# finally in g

Catching exceptions

  • try
  • except
  • finally

def divide(x, y):
    return x/y

def main():
    cnt = 6
    for num in [2, 0, 'a']:
        try:
            divide(cnt, num)
        except ZeroDivisionError:
            pass
        except (IOError, MemoryError) as err:
            print(err)
        else:
            print("This will run if there was no exception at all")
        finally:
            print("Always executes. {}/{} ended.".format(cnt, num))

    print("done")


main()

Output:

This will run if there was no exception at all
Always executes. 6/2 ended.
Always executes. 6/0 ended.
Always executes. 6/a ended.
Traceback (most recent call last):
  File "try.py", line 22, in <module>
    main()
  File "try.py", line 9, in main
    divide(cnt, num)
  File "try.py", line 3, in divide
    return x/y
TypeError: unsupported operand type(s) for /: 'int' and 'str'

Home made exception

You can create your own exception classes that will allow the user to know what kind of an exception was caught or to capture only the exceptions of that type.

class MyException(Exception):
    pass

def some():
    raise MyException("Some Error")

def main():
    try:
        some()
    except Exception as err:
        print(err)
        print("Type: " + type(err).__name__)

    try:
        some()
    except MyException as err:
        print(err)

main()

Output:

Some Error
Type: MyException
Some Error

Home made exception with attributes

class MyException(Exception):
    def __init__(self, name, address):
        self.name  = name
        self.address = address
    def __str__(self):
        return f'Have you encountered problems? name:{self.name}  address:{self.address}'


def some():
    raise MyException(name = "Foo Bar", address = "Somewhere deep in the code")

def main():
    try:
        some()
    except MyException as err:
        print(err.name)
        print(err.address)

        print(err)
        print("Type: " + type(err).__name__)
    except Exception as err:
        print(f"Some other issue {err}")

main()

# Foo Bar
# Somewhere deep in the code
# Have you encountered problems? name:Foo Bar  address:Somewhere deep in the code
# Type: MyException

Home made exception hierarcy

class MyError(Exception):
    pass

class MyGreenError(MyError):
    pass

class MyBlueError(MyError):
    pass


def green():
    raise MyGreenError('Hulk')

def blue():
    raise MyBlueError('Frozen')

def red():
     red_alert()

Home made exception hierarcy - 1

import colors as cl

def main():
    print("start")
    try:
        cl.green()
    except Exception as err:
        print(err)
        print(type(err).__name__)
    print("done")


main()

Output:

start
Hulk
MyGreenError
done

Home made exception hierarcy - 2

import colors as cl

def main():
    print("start")
    try:
        cl.green()
    except cl.MyGreenError as err:
        print(err)
        print(type(err).__name__)
    print("done")


main()

Output:

start
Hulk
MyGreenError
done

Home made exception hierarcy - 3

import colors as cl

def main():
    print("start")

    try:
        cl.green()
    except cl.MyError as err:
        print(err)
        print(type(err).__name__)

    try:
        cl.blue()
    except cl.MyError as err:
        print(err)
        print(type(err).__name__)

    try:
        cl.red()
    except cl.MyError as err:
        print(err)
        print(type(err).__name__)




    print("done")


main()

Output:

start
Hulk
MyGreenError
Frozen
MyBlueError
Traceback (most recent call last):
  File "hierarchy3.py", line 30, in <module>
    main()
  File "hierarchy3.py", line 19, in main
    cl.red()
  File "/home/gabor/work/slides/python/examples/exceptions/colors.py", line 18, in red
    red_alert()
NameError: name 'red_alert' is not defined

Exercise: spacefight with exceptions

Take the number guessing game (or one-dimensional space-fight) and add exceptions for cases when the guess is out of space (0-200 by default), or when the guess is not a number.

import random

class Game:
    def __init__(self):
       self.lower_limit = 0
       self.upper_limit = 200

       self.number = random.randrange(self.lower_limit, self.upper_limit)
       self.is_debug = False
       self.running = True

    def debug(self):
        self.is_debug = not self.is_debug

    def guess(self, num):
        if num == 'd':
            self.debug()
            return

        if self.is_debug:
            print("Hidden number {}. Your guess is {}".format(self.number, num))

        if num < self.number:
            print("Too small")
        elif num > self.number:
            print("Too big")
        else:
            print("Bingo")
            self.running = False


g = Game()
g.guess('d')

try:
    g.guess('z')
except Exception as e:
    print(e)

try:
    g.guess('201')
except Exception as e:
    print(e)

try:
    g.guess('-1')
except Exception as e:
    print(e)

Exercies: Raise My Exception

This is very similar to the exercise the first chapter about exceptions, but in this case you need to create your own hierarchy of exception classes.

  • Write a function that expects a positive integer as its single parameter.
  • Raise exception if the parameter is not a number.
  • Raise a different exception if the parameter is not positive.
  • Raise a different exception if the parameter is not whole number.
  • In each case make sure both the text and the type of the exceptions are different.
  • Include the actual value received as an attribute in the exception object.

Solution: spacefight with exceptions

import random

class SpaceShipError(Exception):
    def __init__(self, inp):
        self.inp = inp

class NumberTooBigError(SpaceShipError):
    def __str__(self):
        return "Number {} is too big".format(self.inp)

class NumberTooSmallError(SpaceShipError):
    def __str__(self):
        return "Number {} is too small".format(self.inp)


class NotANumberError(SpaceShipError):
    def __str__(self):
        return "Not a Number {}".format(self.inp)


class Game:
    def __init__(self):
       self.lower_limit = 0
       self.upper_limit = 200

       self.number = random.randrange(self.lower_limit, self.upper_limit)
       self.is_debug = False
       self.running = True

    def debug(self):
        self.is_debug = not self.is_debug

    def guess(self, num):
        if num == 'd':
            self.debug()
            return

        if self.is_debug:
            print("Hidden number {}. Your guess is {}".format(self.number, num))

        try:
            num =  int(num)
        except Exception:
            raise NotANumberError(num)

        if num > self.upper_limit:
            raise NumberTooBigError(num)

        if num < self.upper_limit:
            raise NumberTooSmallError(num)

        if num < self.number:
            print("Too small")
        elif num > self.number:
            print("Too big")
        else:
            print("Bingo")
            self.running = False


g = Game()
g.guess('d')

try:
    g.guess('z')
except Exception as e:
    print(e)

try:
    g.guess('201')
except Exception as e:
    print(e)

try:
    g.guess('-1')
except Exception as e:
    print(e)



#while g.running:
#    guess = input("Please type in your guess: ")
#    g.guess(int(guess))

Output:

Hidden number 137. Your guess is z
Not a Number z
Hidden number 137. Your guess is 201
Number 201 is too big
Hidden number 137. Your guess is -1
Number -1 is too small

Solution: Raise My Exception

class MyValueError(ValueError):
   def __init__(self, val):
       self.value = val

class MyFloatError(MyValueError):
   def __str__(self):
       return "The given parameter {} is a float and not an int.".format(self.value)

class MyTypeError(MyValueError):
   def __init__(self, val, val_type):
       self.value_type = val_type
       super(MyTypeError, self).__init__(val)

   def __str__(self):
       return "The given parameter {} is of type {} and not int.".format(self.value, self.value_type)

class MyNegativeError(MyValueError):
   def __str__(self):
       return "The given number {} is not positive.".format(self.value)

def positive(num):
   if type(num).__name__ == 'float':
       raise MyFloatError(num)

   if type(num).__name__ != 'int':
       raise MyTypeError(num, type(num).__name__)

   if num < 0:
       raise MyNegativeError(num)

for val in [14, 24.3, "hi", -10]:
   print(val)
   print(type(val).__name__)
   try:
       positive(val)
   except MyValueError as ex:
       print("Exception: {}".format(ex))
       print("Exception type {}".format(type(ex).__name__))

   # Exception, ValueError

Exception finally return


def div(a, b):
    try:
        print("try")
        c = a / b
    except Exception:
        print("exception")
        return
    finally:
        print("finally")

div(2, 1)
print('---')
div(2, 0)