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
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]
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.