process of identifying & reselving errors / bugs
print() func & f-strings¶
understand the state & flow of variables
def add(a,b):
result = a + b
print(f'{a} + {b} = {result}')
# by printing a,b, result -> verify func. working as expected
return result
print(add(4,5))4 + 5 = 9
9
Interactive debugging with pdb module¶
pdb -> interactive debugging environment
import pdb
def divide(a,b):
pdb.set_trace()
# ^
# func. -> step through the code
# -> inspect variables
# -> understand prog. behavior
return a/b
print(divide(10,2))
# running will give
# 1. loc of the file, where the set_trace() is called
# 2. the code immidiately after it
# 3. prompt -> (pdb)
# |-> (pdb) help ---a list of commands
# |-> (pdb) whatis -- understand the type of elements
# |-> (pdb) continue / cont / c -- continue execution of code> /tmp/ipykernel_8582/2391405730.py(9)divide()
7 # -> inspect variables
8 # -> understand prog. behavior
----> 9 return a/b
10
11 print(divide(10,2))
ipdb> c
5.0
IDE debugging tools¶
provide -> visual interface to examine -> state of program
easy to identify & fix issues
Advance debugging tools¶
breakpoints
setp execution
variable ispection
Using VS Code debugger¶
If you use VS Code, you can set breakpoints in your code and run the debugger to pause execution at those points. Here’s how to debug the same divide function:
Step 1: Set up your code¶
Create a file called main.py with the following content:
def divide(a, b):
result = a / b
return result
print(divide(10, 2))
print(divide(15, 3))###Step 2: Set a breakpoint
Click in the gutter (left margin) next to line 2
(result = a / b)to set a breakpointA red dot will appear, indicating the breakpoint is set
###Step 3: Start debugging
Press
F5or go to Run > Start DebuggingSelect “Python File” when prompted
The debugger will pause execution at your breakpoint
###Step 4: Inspect variables
Hover over variables to see their current values
Use the Variables panel on the left to see all local variables
Use the Debug Console at the bottom to evaluate expressions
###Step 5: Step through code
Use the debug toolbar to:
Continue (
F5): Resume execution until the next breakpointStep Over (
F10): Execute the current line and move to the nextStep Into (
F11): Enter into function callsStep Out (
Shift+F11): Exit the current function
Exception Handling¶
process of catching and managing errors that occur during the execution of a program - so code does’t crash unexpectedly
For handling errors py has :
try → block of code - where error might occur
except → runs if an error of the specified type is raised inside the try block.
else → Runs if no exception is raised in the try block.
finally → Runs no matter what, whether or not an exception occurred.
Useful for Clean-up tasks :
closing files
releasing resources
try:
x = 10 / 0
except ZeroDivisionError:
print("You can't divide by zero!")
# dividing by zero raises a ZeroDivisionError,
# which is then caught and handled.You can't divide by zero!
try :
x = 10 / 2
except ZeroDivisionError:
print("You can't divide by zero!")
else :
print('Division successful:',x)
finally :
print('This block always runs.')Division successful: 5.0
This block always runs.
catch multiple exceptions with separate except blocks → make your error responses more specific and useful.
try :
number = int ('abc')
result = 10 / number
except ValueError :
# ^
# exception class
print('That was not a valid number.')
except ZeroDivisionError:
# ^
# exception class
print("Can't divide by zero.")That was not a valid number.
try :
x = 1 / 0
except ZeroDivisionError as e :
# e -> Exception Object - an instance created from that class.
print(f'Error occurred: {e}')Error occurred: division by zero
multiple exceptions in a single except clause by specifying the exceptions as a tuple:
try :
number = int(input('Enter a number: '))
result = 10 / number
except (ValueError, ZeroDivisionError) as e:
print(f'Error occurred:{e}')Enter a number: 0
Error occurred:division by zero
Raise Statement¶
to manually trigger exceptions in your code
to signal that an error condition has occurred or that certain requirements haven’t been met.
usecases:¶
creating robust applications where you need to
enforce business rules
validate input
provide meaningful error messages
raise built-in exceptions or create custom error messages.
def check_age (age):
if age < 0 :
raise ValueError ('Age can\'t be negative')
# ^ |-> ValueError with custom message
# trig. an exception
return age
try:
check_age(-5)
except ValueError as e:
print(f'Error: {e}')Error: Age can't be negative
The raise statement can also be used to re-raise the current exception, which is particularly useful in exception handling → allows to log / perform cleanup while still propagating the error up the call stack.
def process_data(data):
try:
result = int(data)
return result * 2
except ValueError:
print('Logging: Invalid data received')
raise # Re-raises the same ValueError
# keeps the exception alive and,
# pass it back out to wherever the function was called.
try:
process_data('abc')
except ValueError:
print('Handled at higher level')Logging: Invalid data received
Handled at higher level
create and raise custom exceptions by defining your own exception classes:
class InsufficientFundsError(Exception):
def __init__(self, balance, amount): # More study needed
self.balance = balance
self.amount = amount
super().__init__(f'Insuffucient funds: ${balance} avaliable, ${amount} requested')
def withdraw(balance, amount):
if amount > balance :
raise InsufficientFundsError(balance, amount)
return balance - amount
try:
new_balance = withdraw(100,150)
except InsufficientFundsError as e:
print(f'Transaction failed:{e}')
Transaction failed:Insuffucient funds: $100 avaliable, $150 requested
More Study Needed:
The raise statement can also be used with the from keyword to chain exceptions, showing the relationship between different errors:
def parse_config(filename):
try:
with open(filename, 'r') as file:
data = file.read()
return int(data)
except FileNotFoundError:
raise ValueError('Configuration file is missing') from None
except ValueError as e:
raise ValueError('Invalid configuration format') from e
config = parse_config('config.txt')---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/tmp/ipykernel_397/3068554.py in <cell line: 0>()
9 raise ValueError('Invalid configuration format') from e
10
---> 11 config = parse_config('')
/tmp/ipykernel_397/3068554.py in parse_config(filename)
5 return int(data)
6 except FileNotFoundError:
----> 7 raise ValueError('Configuration file is missing') from None
8 except ValueError as e:
9 raise ValueError('Invalid configuration format') from e
ValueError: Configuration file is missingWe can also raise exceptions conditionally with assert statements, which are essentially shorthand for raise with AssertionError:
def calculate_square_root(number):
# assert <condition>, <error_message>
assert number >= 0, 'Cannot calculate square root of negative number'
return number ** 0.5
try:
result = calculate_square_root(-4)
print(result)
except AssertionError as e:
print(f'Assertion failed: {e}')Assertion failed: Cannot calculate square root of negative number