Error Handling
Scriptling provides comprehensive error handling with Python 3-style exception handling.
Error vs Exception
Scriptling has two distinct types of runtime error conditions:
| Aspect | Error | Exception |
|---|---|---|
| Purpose | Fatal runtime errors | Recoverable conditions |
| Can be caught? | No (try/except won’t catch them) | Yes (with try/except) |
| Examples | Parse errors, syntax errors, VM errors | SystemExit, ValueError, user-defined |
| Propagation | Immediately converted to Go error | Propagated for try/except |
Try/Except/Finally
The basic structure for error handling:
try:
# Code that might raise an exception
result = 10 / 0
except ZeroDivisionError:
# Handle division by zero
print("Cannot divide by zero")
finally:
# Always executes (optional)
print("Cleanup code here")Multiple Exception Types
Handle different error types with separate except blocks:
try:
value = int(user_input)
result = 100 / value
except ValueError as e:
print("Invalid number: " + str(e))
except ZeroDivisionError:
print("Cannot divide by zero")
except Exception as e:
print("Unexpected error: " + str(e))Catching with Variable
try:
raise "something went wrong"
except Exception as e:
print("Error: " + str(e))Bare Except
try:
risky_operation()
except:
print("Error occurred") # Catches any exceptionException Type Hierarchy
Scriptling supports Python 3-style exception type matching:
Exception (base class)
├── ValueError - Invalid values
├── TypeError - Type mismatches
├── NameError - Undefined names
├── ZeroDivisionError - Division by zero
├── IndexError - Sequence index out of range
├── KeyError - Dictionary key not found
├── AttributeError - Attribute not found on object
└── ... (more specific types)Built-in Exception Types
| Exception Type | When Raised |
|---|---|
Exception |
Base class for all exceptions |
ValueError |
Invalid value for operation |
TypeError |
Operation on wrong type |
NameError |
Variable/identifier not found |
ZeroDivisionError |
Division or modulo by zero |
IndexError |
Sequence index out of range |
KeyError |
Dictionary key not found |
AttributeError |
Attribute not found on object |
Automatic Exception Type Inference
Scriptling automatically infers exception types from error messages:
try:
x = "string" + 123 # Type mismatch
except TypeError as e:
print("Caught type error") # This works!
try:
x = undefined_variable
except NameError as e:
print("Caught name error") # This works!Raise Statement
Basic Raise
def validate_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age seems unrealistic")
return TrueException Constructors
Built-in exception types can be raised using constructors:
raise Exception("generic error")
raise ValueError("invalid value")
raise TypeError("wrong type")
raise NameError("name not defined")Simple String Raise
if x < 0:
raise "Value must be positive"Re-raising Exceptions
Re-raise an exception after handling:
try:
risky_operation()
except Exception as e:
log_error(e)
raise # Re-raise the same exceptionBare raise outside an except block raises an error:
raise # Error: No active exception to re-raiseRaise with Different Type
Change the exception type while preserving context:
try:
parse_config(data)
except ValueError as e:
raise TypeError("Configuration error: " + str(e))Assert Statement
Test conditions and raise errors when they fail:
# Basic assert - raises AssertionError if condition is False
assert x > 0
# Assert with optional error message
assert x > 0, "x must be positive"
# Common use cases
assert len(data) > 0, "Data cannot be empty"
assert user is not None, "User not found"
assert response.status_code == 200, "Request failed"
# Use in functions for validation
def divide(a, b):
assert b != 0, "Cannot divide by zero"
return a / bException Object Properties
When you catch an exception with as e, you can access its properties:
try:
result = 10 / 0
except Exception as e:
print("Type: " + type(e)) # "EXCEPTION"
print("Message: " + str(e)) # "division by zero"Common Patterns
Safe Dictionary Access
# Option 1: Using try/except
try:
value = data["key"]
except KeyError:
value = default_value
# Option 2: Using get() method (preferred)
value = data.get("key", default_value)Safe List Access
try:
item = items[index]
except IndexError:
item = NoneResource Cleanup with Finally (or with)
Use with when the resource implements __enter__/__exit__:
with open_connection() as conn:
process(conn)
# __exit__ called automatically — no finally neededFall back to try/finally when no context manager is available:
file = None
try:
file = open_file("data.txt")
process_file(file)
except Exception as e:
print("Error: " + str(e))
finally:
if file:
file.close()HTTP Error Handling
import json
import requests
try:
options = {"timeout": 5}
response = requests.get("https://api.example.com/data", options)
if response.status_code != 200:
raise "HTTP error: " + str(response.status_code)
data = json.loads(response.body)
print("Success: " + str(len(data)))
except:
print("Request failed")
data = []
finally:
print("Request complete")Custom Exception Patterns
Creating Custom Error Messages
def validate_user(user):
if not user.get("name"):
raise ValueError("User must have a name")
if not user.get("email"):
raise ValueError("User must have an email")
if "@" not in user["email"]:
raise ValueError("Invalid email format")
return TrueException Chaining
def load_config(path):
try:
data = read_file(path)
return parse_json(data)
except FileNotFoundError:
raise ValueError("Config file not found: " + path)
except JSONParseError as e:
raise ValueError("Invalid config format: " + str(e))SystemExit Exception
The sys.exit() function raises a SystemExit exception that can be caught:
import sys
try:
sys.exit(42)
except Exception as e:
print("Caught: " + str(e)) # "Caught: SystemExit: 42"
# Exit with custom message
sys.exit("Fatal error occurred")Exception Handling in Libraries
When writing libraries, follow these guidelines:
- Document exceptions your functions can raise
- Use specific exception types for different error conditions
- Preserve original exceptions when wrapping errors
- Consider recovery scenarios - can the caller reasonably recover?
# Good library design
def parse_date(date_string):
"""
Parse a date string into components.
Args:
date_string: Date in YYYY-MM-DD format
Returns:
Dict with year, month, day
Raises:
ValueError: If date_string is not valid format
TypeError: If date_string is not a string
"""
if not isinstance(date_string, str):
raise TypeError("date_string must be a string")
# ... parsing logicCommon Pitfalls
Catching Too Broadly
# Bad - catches everything including system exits
try:
some_operation()
except Exception:
pass # Silently ignores all errors
# Good - catch specific exceptions
try:
some_operation()
except (ValueError, TypeError) as e:
log_error(e)
# Handle specific expected errorsSilent Failures
# Bad - silently ignores errors
try:
risky_operation()
except:
pass # What went wrong?
# Good - at least log the error
try:
risky_operation()
except Exception as e:
print("Operation failed: " + str(e))Overly Broad Try Blocks
# Bad - too much code in try block
try:
config = load_config()
connect_database()
process_data()
save_results()
except Exception:
handle_error() # Which part failed?
# Good - narrow try blocks
config = load_config()
connect_database()
try:
process_data() # Just the risky part
except DataError as e:
handle_data_error(e)
save_results()Performance Considerations
Try/Except vs Conditional Checks
# Slower for frequent expected failures
try:
value = dict["key"]
except KeyError:
value = default
# Faster for expected lookups
value = dict.get("key", default)Rule of thumb: Use exceptions for exceptional cases, not for control flow.
For Go Developers
When calling Scriptling from Go:
// Check for Error objects
if obj.IsError(result) {
// Handle fatal error
}
// Check for Exception objects
if ex, ok := result.(*object.Exception); ok {
// Check for SystemExit specifically
if ex.IsSystemExit() {
exitCode := ex.GetExitCode()
// Handle exit
}
}Summary
- Use
try/except/finallyfor structured error handling - Catch specific exception types when possible
- Use exceptions for exceptional cases, not control flow
- Always preserve original exceptions when wrapping errors
- Document which exceptions your functions can raise
- Add context to exceptions to aid debugging
- Avoid silent failures and overly broad exception handlers
See Also
- Functions - Function definitions
- HTTP & JSON - HTTP error handling patterns
- Python Differences - Exception handling differences