Have you ever filled out a web form and it just froze? Or, maybe you had an app that stopped working for no apparent reason. In many cases, these problems are caused by unhandled exceptions. You have probably also encountered one when your micro:bit suddenly stopped working, and the LED display slowly scrolled an error message with a description and line number.
Unhandled exceptions can affect your cyber:bot. Imagine you are in a contest typing navigation commands that a micro:bit transmits over radio to your cyber:bot. Instead of typing a number for wheel speed, you accidentally type some letters. Your cyber:bot script is only expecting numbers, so the letters you type cause an exception. Then, your cyber:bot stops because it didn’t have exception handling statements. Meanwhile, the other team’s cyber:bot passes yours and wins.
Now, imagine that you added exception handling statements that make your script tell you if it receives something other than the numbers it expects, so you try again and enter the numbers correctly. Your team’s cyber:bot keeps executing the script, and your team is still in the race!
This activity shows how to add exception handling statements to your scripts, to prevent your cyber:bot from being the one with a script that freezes during a navigation contest.
You will need:
Complete this tutorial first:
You will understand how exceptions can occur and be able to write exception handling code that keeps the application running.
After this, you will also be ready to move on to the next tutorials.
An “exception to the rule” is a special case or situation where the rule doesn’t apply. With this kind of exception, someone usually points it out, and people normally talk together and figure out the best way to handle the exception.
When your micro:bit’s runtime engine encounters an exception to the rules created by your script, it is also called an exception. But your script can't point it out to you unless you equip it to do that. Statements can be added to scripts telling the micro:bit what to do in response to exceptions. They are often referred to as exception handling statements, or sometimes just exception handling.
When an exception is unhandled, it means there are no statements that catch and correct the problem. In this case, the micro:bit runtime stops executing statements and displays an error message in the LED display with a description and line number. The most important thing about adding exception handling statements is that they can keep your script running after the exception has occurred, and even fix the problem.
Exception handling code has many variations, but the main ingredients are try and except.
try:
# Statements that might cause an exception
except:
# Statements that “handle” the exception so that
# the micro:bit can keep running its script.
A more complete exception handler might look like this
try:
# Statements here that might cause an exception.
except:
# If there was an exception, do something to correct the
# problem here.
else:
# If there was not an exception, do these statements.
finally:
# Regardless of whether or not there was an exception,
# do these statements.
The next example will demonstrate how to test and handle exceptions caused by simple programming errors. The example after that is especially important because it demonstrates how exceptions caused by user errors (like typing mistakes) can be handled. With that kind of error handling, you won’t have to restart your micro:bit because it will handle the error by displaying a helpful message and prompting you to try again.
In this activity, you will start with a working script and then modify it to cause an exception. As usual, the script will halt when it reaches the statement that causes the exception. You will then add statements to “handle” the exception and prevent the program from halting. Finally, you will add statements that display more helpful messages.
# exceptions_division_calc from microbit import * sleep(1000) print("Division Calculation") n = 20 d = 5 q = n / d print("q = n / d") print(" = ", n, " / ", d) print(" = ", q) print("Try again!") print()
Now let's break the script!
Let's break it a different way!
Let's look at the exceptions_division_calc script's arithmetic statements:
n = 20 d = 5 q = n / d
When n and d are integers, the script performs this calculation: q = n / d
So, if n is 20 and d is 5, then q = 20 / 5, and the answer is 4, and the script that solves the problem will report the correct answer.
What happened when you changed d to 0? Then, q = 20 / 0. There is no answer because no number can be multiplied by 0 to result in 20. Since this is a Python script, setting d to 0 will cause a type of exception called a ZeroDivisionError.
Next, what happened when you modified the script to d = “Hello” ? The string "Hello" isn’t even a number! This causes a type of exception called a TypeError.
The micro:bit stops executing the script’s statements at the point where the exception occurs. So, when there is an exception in the program, the micro:bit never gets to the statement that prints the “Try again!” message. But, the ZeroDivisionError and TypeError messages are helpful for debugging code.
The exceptions that caused the micro:bit to stop mid-script can be "handled" with Python's try, except, else, and finally statements. Note that try and except go together, but else and finally are optional. Here is a template for handling code that could cause exceptions:
try:
# Statements here that might cause an exception.
except:
# If there was an exception, do something about it here.
else:
# If there was not an exception, do these statements
finally:
# Regardless of whether or not there was an exception,
# do these statements.
Here a variation of except with statements that display the exception description and type. The exception is stored in the variable e. Printing print("Exception = ", e) shows “error = “ followed by the exception text. et = type(e) stores the type of exception in a variable named et. Printing the et variable with print("Exception type = ", et) displays the exception type in the terminal. Recall that exception types include ZeroDivisionError and TypeError.
except Exception as e: print("Exception = ", e) et = type(e) print("Exception type = ", et)
If you know that certain types of exceptions will occur, you can even add else statements that handle each type of exception differently, like this:
except ZeroDivisionError: print("Can't divide by zero.") except TypeError: print("Expected a number.")
Here are some of the more common exception types/messages you might encounter:
TypeError | When the item is the wrong type |
ValueError | When the item is the wrong type, as in it needs to be compatible with math operators |
NameError | When the item has not been defined |
ZeroDivisionError | When division by zero is attempted |
IndexError | When attempting to access an invalid index |
KeyError | When a key is not found |
StopIteration | When the next function goes past its limit |
The division script we have been using includes statements that will fit into each part of a full exception-handling compound statement.
# exceptions_division_calc_try_this from microbit import * sleep(1000) print("Division Calculation") try: n = 20 d = 5 q = n / d except Exception as e: print("Exception = ", e) et = type(e) print("Exception type = ", et) else: print("q = n / d") print(" = ", n, " / ", d) print(" = ", q) finally: print("Try again!") print()
Do you remember how to break the script?
Note that the script did not halt at the exception. Instead, it displayed the exception, but continued to print "Try again!"
Now let's break it with text one more time.
Note again that the script did not halt at the statement that caused the exception. It made it all the way through printing "Try again!"
A try...except...else...finally statement can have more than one except. Also, each except can have one or more statements that address specific errors. For example, instead of displaying the exception information, you can display a message that explains how to solve the problem.
except Exception as e: print("Exception = ", e) et = type(e) print("Exception type = ", et)
...with this:
except ZeroDivisionError: print("Can't divide by zero.") except TypeError: print("Expected a number.")
Now let's repeatt the tests three times, changing the value of d each time:
In our last example, the programmer (you) introduced the exception right in the script. But in real life applications, the exception may be caused by a user entering incorrect or unexpected data during run time. And, the user can't necessarily see the code, or understand Python's error types.
The next example program works much like the previous ones, but it lets the user change the value of d — the denominator— by entering data in the terminal instead of modifying the script.
# exceptions_user_division_calc from microbit import * sleep(1000) print("Division Calculations") while True: text = input("Enter numerator n: ") n = float(text) text = input("Enter denominator d: ") d = float(text) q = n / d print("Quotient q: ") print("q = n / d") print(" = ", n, " / ", d) print(" = ", q) print("Try again!") print()
This type of application is explained in detail in Computer - micro:bit Talk [2], leading up to the apps in the Input/Print for Applications [5] page. So, let’s just look at the exceptions that occurred.
Like the previous activity, when you typed 0 and pressed enter, a ZeroDivisionError exception occurred at the q = n / d statement since it cannot give you an answer with d = 0. No surprise here.
Next, when you tried “Hello" in the denominator, a new error occurred—the ValueError. This is different from TypeError we saw previously. In our earlier example that used the terminal, d was defined as an int. Here, d is defined as a float.
So, here the code froze when it tried to execute float(“Hello”). The float function can convert text that represents a value into that value. So, in d = float("5"), the float function receives the text string “5” and returns the value 5. That value gets stored in the variable d.
If a variable named text stores the string “5”, then d = float(text) will also store the value 5 in d. In contrast, d = float(“Hello!”) has non-digits that the float function cannot convert to a number. At that point, the MicroPython runtime raises the ValueError exception.
While developing the app, it’s still a good idea to to display the error and type so that you can attempt to “break” the code with different variations of user input errors.
Your script can selectively address more than one kind of exception with a single action. For example, if you want it to display the “Expected a number.” message for either a TypeError or a ValueError, you could use this:
except (TypeError, ValueError): print("Expected a number.")
The first step to preventing a user from causing an exception is to test as many user mistakes as you can think of. Make sure to record each type of exception that occurred. You will need that list later so that each exception type can be handled properly.
To speed up the process, use the except Exception as e block, and print the exception’s text and type. This will allow you to keep the app running as you test various user errors that cause exceptions. Here is an example.
# exceptions_user_division_calc_try_this.py from microbit import * sleep(1000) print("Division Calculations") while True: try: text = input("Enter numerator n: ") n = float(text) text = input("Enter denominator d: ") d = float(text) q = n / d except Exception as e: print("Exception = ", e) et = type(e) print("Exception type = ", et) else: print("Quotient q: ") print("q = n / d") print(" = ", n, " / ", d) print(" = ", q) finally: print("Try again!") print()
Now that you know what exception types to expect, you can modify your application to handle each one by responding with helpful user prompts. Your customers will thank you one day!
except Exception as e: print("Exception = ", e) et = type(e) print("Exception type = ", et)
...to this:
except (TypeError, ValueError): print("Expected a number.") except ZeroDivisionError: print("Can't divide by zero.")
Links
[1] https://python.microbit.org/v/2
[2] https://learn.parallax.com/tutorials/robot/cyberbot/computer-microbit-talk
[3] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-navigation-control-keyboard
[4] https://learn.parallax.com/tutorials/robot/cyberbot/cybersecurity-radio-tilt-control
[5] https://learn.parallax.com/tutorials/robot/cyberbot/computer-microbit-talk/inputprint-applications