Lesson 14 - Error Handling (try-except)
Learning Objective
Catch and handle specific exceptions with try / except, use else and finally, and raise your own exceptions when a check fails.
Introduction
Programs don’t always run perfectly.
Users enter unexpected values.
Files may be missing.
Network connections can fail.
Robots may become unreachable.
Without error handling, these situations cause a program to crash immediately.
Python provides exceptions and the try / except mechanism so programs can respond gracefully when something goes wrong.
Instead of crashing, your code can:
- Display a helpful message
- Retry an operation
- Use a fallback value
- Skip bad input
- Clean up resources before exiting
In this lesson you’ll learn:
- How exceptions work
- How to catch specific errors
- When to use
else - When to use
finally - How to raise your own exceptions
What Is an Exception?
An exception is an error that occurs while a program is running.
Example:
10 / 0Produces:
ZeroDivisionError: division by zeroAnother example:
int("abc")Produces:
ValueError: invalid literal for int()If these exceptions aren’t handled, the program stops immediately.
Catching Exceptions
The simplest pattern is:
try:
risky_operation()
except SomeException:
handle_problem()Example:
try:
number = int("abc")
except ValueError:
print("That wasn't a valid number.")Output:
That wasn't a valid number.The program continues running instead of crashing.
Code
# error_handling.py
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Cannot divide by zero.")
return None
else:
print("Division succeeded.")
return result
finally:
print("Division attempt finished.")
print(safe_divide(10, 2))
print(safe_divide(10, 0))
# Catching a specific exception type across a loop
values = ["12", "abc", "7"]
for value in values:
try:
number = int(value)
print(f"Converted: {number}")
except ValueError:
print(f"'{value}' is not a valid number.")Expected Output
Click to see expected output
Division succeeded.
Division attempt finished.
5.0
Cannot divide by zero.
Division attempt finished.
None
Converted: 12
'abc' is not a valid number.
Converted: 7Understanding the Flow
Successful Case
safe_divide(10, 2)Flow:
try
↓
else
↓
finallyResult:
Division succeeded.
Division attempt finished.
5.0Failure Case
safe_divide(10, 0)Flow:
try
↓
except
↓
finallyResult:
Cannot divide by zero.
Division attempt finished.
NoneCommon Exception Types
Raised when a value has the right type but an invalid value.
int("abc")Raised when dividing by zero.
10 / 0Raised when opening a file that doesn’t exist.
open("missing.txt")Catching Specific Errors
Good practice:
try:
number = int(text)
except ValueError:
print("Invalid number.")Avoid:
try:
number = int(text)
except:
print("Something went wrong.")The second version hides all errors, including bugs you didn’t expect.
Specific exceptions make debugging much easier.
Using else
else runs only if the try block succeeds.
Example:
try:
result = 10 / 2
except ZeroDivisionError:
print("Bad division")
else:
print("Success!")Output:
Success!The advantage is that successful code stays separate from error-handling code.
Using finally
finally always executes.
Even if:
- An exception occurs
- The function returns early
- The program leaves the
tryblock
Example:
try:
print("Working")
finally:
print("Cleanup")Output:
Working
CleanupThis makes finally ideal for cleanup tasks.
Raising Your Own Exceptions
Sometimes Python doesn’t know something is wrong.
Example:
age = -5A negative age is invalid, but Python doesn’t automatically raise an error.
You can raise one yourself:
if age < 0:
raise ValueError("Age cannot be negative")Output:
ValueError: Age cannot be negativeThis allows your code to enforce rules and validate data.
🔧 Under the Hood
What’s the difference between else and finally?
Both appear after a try block, but they serve different purposes.
else
Runs only if the try block completed successfully.
Example:
try:
result = 10 / 2
except ZeroDivisionError:
print("Error")
else:
print("Success")Output:
SuccessIf an exception occurs:
10 / 0the else block is skipped entirely.
finally
Runs no matter what.
Example:
try:
10 / 0
except ZeroDivisionError:
print("Error")
finally:
print("Cleanup")Output:
Error
CleanupEven if the function returns early:
try:
return 42
finally:
print("Cleanup")the finally block still runs.
Why This Matters
Use:
elsefor code that should execute only after success.
Use:
finallyfor code that must execute regardless of success or failure.
Common examples:
- Closing files
- Closing network connections
- Releasing hardware resources
- Stopping robot motion safely
Why Avoid Bare except?
Bad:
except:
passThis catches:
ValueErrorNameErrorTypeError- Unexpected bugs
all at once.
Good:
except ValueError:This catches only the error you’re prepared to handle.
Everything else remains visible during debugging.
Student Challenge
Challenge 1 — Python Only
Validate a list of ages while handling both invalid text and manually-raised errors.
- Loop through entries
["25", "-4", "abc", "40"]. - Convert each string to an integer with
int(). Ifage < 0, manually raise aValueError("Age cannot be negative"). - Catch
ValueErrorexceptions usingexcept ValueError as exc:and print a skipping message.
Hint
Exceptions you raise yourself:
raise ValueError(...)behave exactly like exceptions raised automatically by Python.
They can be caught by the same except ValueError: block.
Click to see solution
# safe_ages.py
entries = ["25", "-4", "abc", "40"]
for entry in entries:
try:
age = int(entry)
if age < 0:
raise ValueError("Age cannot be negative")
print(f"Valid age: {age}")
except ValueError as exc:
print(f"Skipping '{entry}': {exc}")Expected Output:
Valid age: 25
Skipping '-4': Age cannot be negative
Skipping 'abc': invalid literal for int() with base 10: 'abc'
Valid age: 40Challenge 2 — Robot
Catch a real robot connection failure and check whether a servo command was rejected.
- Wrap your
BonicBotconnection in atryblock and catchBonicBotConnectionError(imported frombonicbot_bridge.exceptions). - Attempt to move the left arm to an out-of-bounds angle (
shoulder=300, elbow=20) and check ifmove_left_armreturnsFalse(indicating a rejected move). - Try changing the host address to an invalid address to confirm connection errors are caught gracefully.
Explore additional exception types in the Python SDK reference.
Hint
Python already includes a built-in exception named:
ConnectionErrorThis import:
ConnectionError as BonicBotConnectionErroravoids confusion between the built-in exception and the SDK-specific version.
Click to see solution
# challenge_safe_connect.py
from bonicbot_bridge import BonicBot
from bonicbot_bridge.exceptions import (
ConnectionError as BonicBotConnectionError
)
try:
with BonicBot(
host='192.168.0.188',
timeout=5
) as bot:
# 300° is outside the
# left shoulder's (-45, 180) range
success = bot.move_left_arm(
shoulder=300,
elbow=20
)
if not success:
print(
"Arm move rejected — "
"angle was outside "
"the servo's limits."
)
bot.drive_distance(
0.3,
speed=0.2
)
except BonicBotConnectionError as exc:
print(
f"Could not connect "
f"to the robot: {exc}"
)Error Handling Best Practices
Catch Specific Exceptions
except ValueError:instead of:
except:Use Exceptions for Exceptional Situations
Good:
raise ValueError("Invalid age")Bad:
raise Exception("Everything")Use meaningful exception types whenever possible.
Clean Up with finally
finally:
connection.close()Cleanup code belongs here.
Don’t Hide Bugs
Avoid:
except:
passThis can make debugging extremely difficult.
Reflection Question
A bad connection raises a
ConnectionErrorthat you must catch withexcept, but an out-of-range servo angle simply returnsFalseand prints a warning. Why might the SDK’s designers have chosen two different error-handling styles for these two situations?
By the end of this lesson, you should be comfortable catching exceptions, using try, except, else, and finally, raising your own exceptions, and deciding when an error should be handled versus when it should stop execution.