MicroPython Exceptions

Contents

Introduction

MicroPython is a “slim” version of Python specifically designed with a small footprint to efficiently run on memory constrained microcontrollers.

All code examples are original and have been tested on a micro:bit utilising the Mu Editor

MicroPython Errors

A MicroPython program terminates as soon as it encounters an error unless the program can utilise some mechanism to recover. In MicroPython, an error can be a syntax error or an exception.

Syntax Errors

A syntax error occurs when MicroPython detects a statement that in someway doesn't conform to the language standard. With the exception of some rare occasions a program cannot recover from such an error and the MicroPython interpreter will stop program execution. This will be explored further when discussing the SyntaxError exception.

Example 1

# This program will terminate with a syntax error

phrase1 = 'Hello there!,'
phrase2 = 'my name is John Smith.'
sentence = phrase1 + ' ' + phrase2
prins(sentence)

            
Output 1A:

Traceback (most recent call last):
  File "main.py", line 6, in <module>
NameError: name 'prins' isn't defined

            

The reason for this error is easy to spot! It's fairly obvious that prins should in fact be print. Fixing this error in the code and running the program again gives the expected output.

Output 1B:

Hello there!, my name is John Smith.

            

Exception Errors

An exception error occurs whenever syntactically correct MicroPython code results in an error. MicroPython comes with various built-in exceptions (represented as a hierarchy of classes) as well as the possibility to create self-defined exceptions.

A program can catch and respond to the exception errors. If the program ignores the exception then MicroPython's default exception-handling behaviour kicks in: the program stops and a message is printed.

Example 2A

# This program will terminate with
# a variable type error.

# A program that calculate the area of
# circles given the radius.

# A list of circle radii will be
# processed, with the area calculated
# for each circle then printed out.

from math import pi

# Define a list of circle radii
radii = [5, 3.15, 26, 'k', 69]

            
Output 2A:

Radius = 5 : Area = 78.53982
Radius = 3.15 : Area = 31.17246
Radius = 26 : Area = 2123.717
Traceback (most recent call last):
  File "main.py", line 13, in <module>
TypeError: can't convert 'int' object
           to str implicitly

            

The TypeError exception has occurred because the string 'k' (obviously not a numeric value) has been found in the list of circle radii.

Handling Exceptions

MicroPython handles exception errors in a similar manner to many other computer languages with the try..except..else..finally construct. The syntax in broad terms is:


try:
    code block
except [exception_1, ...exception_n] [as err_obj]:
    code block
else:
    code block
finally:
    code block

          
Python try..except..else..finally
FIG 1 - MicroPython Exception Handling[1]

try Block

Any code where exceptions need to be captured and handled without MicroPython terminating the program prematurely should appear in a try block.

except Block

If an exception occurs in a try block MicroPython will transfer code execution to the except block. This code will be responsible for recovering from the error, logging the error, informing the user and so on.

The accept clause has several different forms. Examples include:


# CASE 1:This will capture all and sundry
except:

#  CASE 2: Specifies a single exception types
except ZeroDivisionError:

# CASE 3: Specifies several exception types
except (ZeroDivisionError, TypeError):

# CASE 4: Exception details in exception object
except (ZeroDivisionError, TypeError) as err_obj:

            
Figure 2: Various forms of except clasue

It is considered very bad practice to use except without specifying an exception type as shown in the first example above.

Each try block may also have more than one except block. For example:


try:
    code block
except ZeroDivisionError:
    code block
except TypeError:
    code block

            

The CASE 4 example from Figure 2 is commonly used. In this form when an exception occurs the error detail is found in the error object. Consider the following example:

Example: except with error object

# Demonstrates use of error object
# in 'except' clause.

# Sums all valid numbers in a list.
# Any item in non-numerical item
# in the list will be caught with
# a handled exception.

# Define the list of numbers to be summed
list1 = [1, 2, 'k', 3, 4, 5]
sum = 0

for element in list1:
    try:
        sum += element
    except (TypeError) as err_obj:
       # Catch any non-numerical value
        # and deal with it. Program execution
        # will then continue.
        print("Error with item '", element, "'")
        print(err_obj)
        print('Processing will continue...')

print('Sum of numbers:', sum)
            
Output:

Error with item ' k '
unsupported types for __iadd__: 'int', 'str'
Processing will continue...
Sum of numbers: 15
            

When the non-numerical k is reached an exception will be thrown. The except block handles the error then the program continues to run. The error object err_obj contains the description of the error and in this case is simply printed out for the user's information.

else Block

The else block of code will run only if the the try block of code has completed without an exception being raised. It is optional i.e. it is not a language requirement that a try..except construct must have an else clause.

finally Block

The finally code block will always run regardless of whether an exception has been raised in the try block. It is also optional.

Example 2A has been rewritten to capture and programmatically handle the TypeError exception.

Example 2B

# Demonstrates use of 'try..except'
# to handle any error.

# A program that calculate the area of circle
# given the radius.

# A list of circle radii will be processed
# with the area calculated for each circle
# then printed out.

from math import pi

# Circle radii
radii = [5, 3.15, 26, 'k', 69]
complete = False

try:
    for radius in radii:
        area = pi * radius ** 2
        print('R =', radius, ': A =', area)
    complete = True

except (ZeroDivisionError, TypeError) as error:
    print("Error with element:'", radius, "'")
    print(error)

else:
    print('All items processed successfully')

finally:
    if complete:
        print('Processing completed')
    else:
        print('Processing halted')

            
Output 2B:

R = 5 : A = 78.53982
R = 3.15 : A = 31.17246
R = 26 : A = 2123.717
Error with element:' k '
can't convert 'int' object to str implicitly
Processing halted
            

Example 2B now terminates under program control. This is an improvement over Example 2A which was halted by MicroPython's default exception handling mechanism.

However it still has a problem. After the program has dealt with the TypeError exception it does not continue processing circle radii. Once the try block has been exited for entry into the except block, program execution cannot re-enter the try block.

Example 2C fixes this issue by moving the for loop out of the try block.

Example 2C

# Demonstrates use of 'try..except'
# to handle any error. An error will
# not cause the program to stop.

# A program that calculate the area of circle
# given the radius.

# A list of circle radii will be processed
# with the area calculated for each circle
# then printed out.

from math import pi

# Circle radii
radii = [5, 3.15, 26, 'k', 69]
errors = 0


for radius in radii:
    try:
        area = pi * radius ** 2
        print('R =', radius, ': A =', area)
        
    except TypeError as error:
        print("Error with element:'", radius, "'")
        print(error)
        errors += 1

print('Processing complete:', errors, 'error(s)')
            
Output 2C:

R = 5 : A = 78.53982
R = 3.15 : A = 31.17246
R = 26 : A = 2123.717
Error with element:' k '
can't convert 'int' object to str implicitly
R = 69 : A = 14957.12
Processing complete: 1 error(s)
            

Manually Raised Exceptions

Up to this point it has been the role of the MicroPython interpreter to raise (trigger) an exception in a try block and program code in an except block has handled the error. It is possible for the program to manually raise an error with the use of the keywords assert and raise.

assert

The keyword assert is used to manually trigger an exception of type AssertionError if a given condition evaluates to False

The syntax and use of assert is quite simple.

assert <condition>[, <error message>]

where:
    (1) If <condition> evaluates to False
        then an AssertionError exception
        will be thrown.

    (2) An optional error message,
        <error message> can be included.

Example:
    try:
        do some things       
        assert (x > 10), 'Number must be > 10'
        do more things
    except  AssertionError as error:
        print(error)

Explanation:
    if the value of x is > 10
    then the try block continues to execute.
    If the value of x is <= 10
    then an exception is thrown
    and the error message is printed.
            
Example 2D

# A simple example demonstrating the
# use of the keyword 'assert'
x = "hello"
# if condition returns False, AssertionError is raised:
assert x == "goodbye", "x should be 'hello'"
            
Output 2D

Traceback (most recent call last):
  File "main.py", line 5, in <module>
AssertionError: x should be 'hello'
            

Example 2C has been rewritten to use assert to check that a radius value is numerical before calculating the circle's area.

Example 2E

# Demonstrates use of 'assert' to manually
# throw an exception.

# A program that calculate the area of
# circles given the radius.
# A list of circle radii will be processed
# with the area calculated for each circle
# then printed out.

# Each radius value is checked that it
# is numeric.
# If the radius is a number then the
# circle's area is calculated and printed.
# If it isn't numeric then an AssertionError
# is raised and handled gracefully.

from math import pi

def isNumeric(i):
    # Returns True if value is numeric
    return (type(i) is int) or (type(i) is float)

# Circle radii
radii = [5, 3.15, 26, 'k', 69]
errors = 0
ErrMsg = ': Is not numeric'

for radius in radii:
    try:
        # Calculate circle area only
        # if radius is numeric.
        assert (isNumeric(radius)), ErrMsg
        area = pi * radius ** 2
        print('Radius =', radius,
              ': Area =', area)
    except  AssertionError as error:
        # Radius is not numeric
        print(radius, error)
        errors += 1

print('Processing complete:', errors, 'error(s)')
            
Output 2E:

Radius = 5 : Area = 78.53982
Radius = 3.15 : Area = 31.17246
Radius = 26 : Area = 2123.717
k is not numeric
Radius = 69 : Area = 14957.12
Processing complete: 1 error(s)
            

raise

While the assert statement evaluates a conditional clause to determine whether or not to throw an exception, the raise statement immediately and unconditionally throws an exception.

The syntax is also simple to use.


raise <exception type>([<error message>])

Where:
    (1) The <exception type> must be one
        that MicroPython recognises.
        See Appendix below for a list of 
        recognised exceptions.

    (2) An optional error message,
        <error message> can be included.

Example 2E has been slightly modified, from using assert to check that a radius value is numerical before calculating the circle's area, to using raise to perform the same numerical check.

Example 2F

# Demonstrates use of 'raise' to manually
# throw an exception.

# A program that calculate the area of
# circles given the radius.
# A list of circle radii will be processed
# with the area calculated for each circle
# then printed out.

# Each radius value is checked that it
# is numeric.
# If the radius is a number then the
# circle's area is calculated and printed.
# If it isn't numeric then an exception
# is raised and handled gracefully.

from math import pi

def isNumeric(i):
   # Returns True if value is numeric
    return (type(i) is int) or (type(i) is float)

# Circle radii
radii = [5, 3.15, 26, 'k', 69]
errors = 0
ErrMsg = 'is not numeric'

for radius in radii:
    try:
        # Calculate circle area only
        # if radius is numeric.
        if not isNumeric(radius):
            raise ValueError(ErrMsg)
        area = pi * radius ** 2
        print('Radius =', radius,
              ': Area =', area)
              
    except  ValueError as error:
        # Radius is not numeric
        print(radius, error)
        errors += 1

print('Processing complete:', errors, 'error(s)')
            
Output 2F:

Radius = 5 : Area = 78.53982
Radius = 3.15 : Area = 31.17246
Radius = 26 : Area = 2123.717
k is not numeric
Radius = 69 : Area = 14957.12
Processing complete: 1 error(s)
            

As Example 2E and Example 2F show, it really is an individual programmer's choice whether to use assert or raise to capture any non-numerical values before the area calculation is done.

Appendix - MicroPython Built-in Exceptions

The table below shows built-in exceptions that are usually raised in MicroPython:[2]



Table 1: MicroPython Built-in Exceptions
Exception Description
ArithmeticError Raised when an error occurs in numeric calculations
AssertionError Raised when an assert statement fails
AttributeError Raised when attribute reference or assignment fails
Exception Base class for all exceptions
EOFError Raised when the input() method hits an "end of file" condition (EOF)
GeneratorExit Raised when a generator is closed (with the close() method)
ImportError Raised when an imported module does not exist
IndentationError Raised when indentation is not correct
IndexError Raised when an index of a sequence does not exist
KeyError Raised when a key does not exist in a dictionary
KeyboardInterrupt Raised when the user presses Ctrl+c, Ctrl+z or Delete
LookupError Raised when errors raised can't be found
MemoryError Raised when a program runs out of memory
NameError Raised when a variable does not exist
NotImplementedError Raised when an abstract method requires an inherited class to override the method
OSError Raised when a system related operation causes an error
OverflowError Raised when the result of a numeric calculation is too large
RuntimeError Raised when an error occurs that do not belong to any specific exceptions
StopIteration Raised when the next() method of an iterator has no further values
SyntaxError Raised when a syntax error occurs
SystemExit Raised when the sys.exit() function is called
TypeError Raised when two different types are combined
ValueError Raised when there is a wrong value in a specified data type
ZeroDivisionError Raised when the second operator in a division is zero

NOTE: Some of these exceptions such as SyntaxError and IndentationError might never be encountered under normal circumstances. They will be picked up by the MicroPython parser during generation of the bytecode before program execution actually begins.