JavaScript – Chapter 2: Development Environment
Learn what JavaScript is, where it runs, why it is important, and how beginners can start using it with simple examples and clear output.
Main reading content
Chapter 34: Debugging Python Programs
A complete beginner-friendly guide to finding, understanding, and correcting syntax errors, runtime errors, logic mistakes, exceptions, and unexpected program behavior.
Chapter 34 Topics
```- 34.1 Understanding Bugs
- 34.2 Syntax Bugs
- 34.3 Runtime Bugs
- 34.4 Logic Bugs
- 34.5 Reading Error Messages
- 34.6 Reading Tracebacks
- 34.7 Debugging with
print() - 34.8 Using
breakpoint() - 34.9 The Python Debugger
- 34.10
pdb - 34.11 VS Code Debugger
- 34.12 Breakpoints
- 34.13 Conditional Breakpoints
- 34.14 Stepping Through Code
- 34.15 Inspecting Variables
- 34.16 Call Stacks
- 34.17 Debugging Exceptions
- 34.18 Remote Debugging Concepts
- 34.19 Rubber Duck Debugging
- 34.20 Debugging Strategies
- 34.21 Chapter Practice Exercises
- 34.22 Chapter Debugging Project
34.1 Understanding Bugs
```A bug is a problem in a program that causes incorrect behavior, an error message, unexpected output, or a complete program failure. Bugs may be caused by typing mistakes, incorrect assumptions, invalid data, missing conditions, or misunderstood requirements.
Debugging is the process of locating the cause of a problem, understanding why it happens, correcting it, and checking that the correction does not create another problem. Debugging is a normal part of programming, even for experienced developers.
Buggy Example
price = 20
```
quantity = 3
# The programmer intended to calculate 13% tax.
tax = price * quantity * 13
total = price * quantity + tax
print("Total:", total)
```
Incorrect Output
Total: 840
Corrected Example
price = 20
```
quantity = 3
subtotal = price * quantity
# Use 0.13 to represent 13 percent.
tax = subtotal * 0.13
total = subtotal + tax
print("Subtotal:", subtotal)
print("Tax:", tax)
print("Total:", total)
```
Correct Output
Subtotal: 60
```
Tax: 7.8
Total: 67.8
```
Explanation
The program ran without displaying an exception, but the formula used 13 instead of 0.13. This is a logic bug because Python successfully followed the instructions, but the instructions were incorrect.
```34.2 Syntax Bugs
```A syntax bug happens when code does not follow Python's grammar rules. Common causes include missing colons, unmatched parentheses, missing quotation marks, incorrect indentation, and misspelled keywords.
Python normally detects syntax problems before running the program. The error message usually points near the place where Python became unable to understand the code, although the real mistake may appear slightly earlier.
Buggy Example
name = "Sara"
```
if name == "Sara"
print("Welcome, Sara")
```
Error
SyntaxError: expected ':'
Corrected Example
name = "Sara"
```
if name == "Sara":
print("Welcome, Sara")
```
Output
Welcome, Sara
Another Syntax Bug
message = "Learning Python
```
print(message)
```
Error
SyntaxError: unterminated string literal
Corrected Version
message = "Learning Python"
```
print(message)
```
Output
Learning Python
```
34.3 Runtime Bugs
```A runtime bug occurs after Python successfully understands the program and begins executing it. The program may work for some values but fail when it encounters invalid input or an unexpected condition.
Common runtime errors include division by zero, missing files, invalid list indexes, incorrect type operations, and attempts to use names that do not exist.
Buggy Example
total = 100
```
number_of_students = 0
average = total / number_of_students
print(average)
```
Error
ZeroDivisionError: division by zero
Corrected Example
total = 100
```
number_of_students = 0
if number_of_students == 0:
print("The average cannot be calculated.")
else:
average = total / number_of_students
print("Average:", average)
```
Output
The average cannot be calculated.
Another Runtime Error
names = ["Sara", "Michael"]
```
print(names[5])
```
Error
IndexError: list index out of range
```
34.4 Logic Bugs
```A logic bug occurs when a program runs without an exception but produces the wrong result. These bugs can be more difficult to find because Python may not display any error message.
Logic bugs often involve incorrect formulas, wrong comparison operators, incorrect loop boundaries, misplaced indentation, or conditions written in the wrong order.
Buggy Example
score = 95
```
if score >= 60:
grade = "D"
elif score >= 70:
grade = "C"
elif score >= 80:
grade = "B"
elif score >= 90:
grade = "A"
else:
grade = "F"
print("Grade:", grade)
```
Incorrect Output
Grade: D
Corrected Example
score = 95
```
# Check the highest range first.
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"
print("Grade:", grade)
```
Correct Output
Grade: A
Explanation
In the buggy version, 95 satisfies the first condition because it is greater than 60. Python does not check later branches after finding a true condition.
```34.5 Reading Error Messages
```Python error messages usually identify the exception type and provide a short description. The exception type tells you the general category of the problem, while the description gives more specific information.
Beginners should read the final line first. Then examine the filename, line number, and highlighted expression. Avoid changing unrelated code before understanding what the message is reporting.
Example
price = "20"
```
quantity = 3
total = price + quantity
print(total)
```
Error Message
Traceback (most recent call last):
```
File "shop.py", line 4, in
total = price + quantity
TypeError: can only concatenate str (not "int") to str
```
How to Read It
shop.pyis the filename.- Line 4 is where the failure occurred.
TypeErroris the exception category.- The program tried to combine a string and an integer.
Corrected Example
price = 20
```
quantity = 3
total = price + quantity
print(total)
```
Output
23
Common Error Types
SyntaxError: Python cannot understand the code structure.NameError: A name has not been defined.TypeError: An operation received an unsuitable type.ValueError: The type is acceptable, but the value is invalid.IndexError: A sequence index does not exist.KeyError: A dictionary key does not exist.AttributeError: An object does not provide the requested attribute.FileNotFoundError: The requested file could not be found.
34.6 Reading Tracebacks
```A traceback shows the sequence of function calls that led to an exception. Each entry normally includes a filename, line number, function name, and source-code line.
Read the final line to identify the exception. Then move upward through the traceback to understand which function called which other function. The lowest entry from your own code is often the immediate failure location.
Example
def calculate_average(total, count):
return total / count
```
def create_report(scores):
total = sum(scores)
count = len(scores)
```
return calculate_average(total, count)
```
def main():
scores = []
```
print(create_report(scores))
```
main()
```
Traceback
Traceback (most recent call last):
```
File "report.py", line 20, in
main()
File "report.py", line 17, in main
print(create_report(scores))
File "report.py", line 9, in create_report
return calculate_average(total, count)
File "report.py", line 2, in calculate_average
return total / count
ZeroDivisionError: division by zero
```
Call Order
- The module called
main(). main()calledcreate_report().create_report()calledcalculate_average().calculate_average()attempted division by zero.
Corrected Function
def calculate_average(total, count):
if count == 0:
return 0
return total / count
```
34.7 Debugging with print()
```
Print debugging means temporarily displaying variable values, program locations, conditions, and function arguments. It is one of the simplest ways to understand what a program is doing.
Debug messages should be descriptive. Instead of printing only a number, print the variable name and value. Remove unnecessary debugging output after the problem is corrected, or replace it with proper logging.
Buggy Program
prices = [10, 20, 30]
```
total = 0
for price in prices:
total = price
print("Total:", total)
```
Incorrect Output
Total: 30
Add Debugging Prints
prices = [10, 20, 30]
```
total = 0
print("Starting total:", total)
for price in prices:
print("Current price:", price)
print("Total before assignment:", total)
```
total = price
print("Total after assignment:", total)
print("-" * 30)
```
print("Final total:", total)
```
Debug Output
Starting total: 0
```
Current price: 10
Total before assignment: 0
Total after assignment: 10
--------------------------
Current price: 20
Total before assignment: 10
Total after assignment: 20
--------------------------
Current price: 30
Total before assignment: 20
Total after assignment: 30
--------------------------
Final total: 30
```
Corrected Program
prices = [10, 20, 30]
```
total = 0
for price in prices:
total += price
print("Total:", total)
```
Correct Output
Total: 60
```
34.8 Using breakpoint()
```
The built-in breakpoint() function pauses a running Python program and opens the configured debugger. By default, it normally starts the Python debugger.
While paused, you can inspect variables, execute expressions, move through the program, and continue execution. This avoids adding many temporary print statements.
Example
def calculate_discount(
price,
discount_rate
```
):
discount = price * discount_rate
```
# Pause the program here.
breakpoint()
final_price = price - discount
return final_price
```
result = calculate_discount(
100,
0.20
)
print("Final price:", result)
```
Debugger Session
> example.py(10)calculate_discount()
```
-> final_price = price - discount
(Pdb) price
100
(Pdb) discount_rate
0.2
(Pdb) discount
20.0
(Pdb) continue
Final price: 80.0
```
Explanation
The program pauses before calculating the final price. Typing variable names displays their current values. The continue command resumes normal execution.
34.9 The Python Debugger
```A debugger is a tool that controls a program while it runs. It can pause execution, inspect variables, execute one line at a time, enter functions, leave functions, and display the call stack.
Debuggers are especially useful when the program contains several functions or when temporary print statements would create too much output.
Common Debugger Actions
- Pause at a selected line.
- Continue until the next breakpoint.
- Step over the current line.
- Step into a function call.
- Step out of the current function.
- Inspect local and global variables.
- Evaluate expressions.
- View the chain of active function calls.
Example Program
def apply_tax(
subtotal,
tax_rate
```
):
tax = subtotal * tax_rate
return subtotal + tax
def calculate_order(
price,
quantity
):
subtotal = price * quantity
total = apply_tax(
subtotal,
0.13
)
```
return total
```
print(calculate_order(25, 3))
```
Output
84.75
A debugger can pause inside calculate_order(), inspect subtotal, step into apply_tax(), and then inspect the calculated tax.
34.10 pdb
```
The pdb module is Python's built-in command-line debugger. It can run scripts, pause at breakpoints, inspect values, and step through code.
It is useful when a graphical debugger is unavailable, when working in a terminal, or when debugging small scripts quickly.
Run a Program with pdb
python -m pdb program.py
Example Program
def divide(first, second):
result = first / second
return result
```
answer = divide(20, 4)
print(answer)
```
Useful pdb Commands
n: Execute the next line.s: Step into a function.r: Continue until the current function returns.c: Continue execution.p expression: Print an expression.pp expression: Pretty-print an expression.l: List nearby source code.w: Display the call stack.u: Move up the call stack.d: Move down the call stack.b line_number: Set a breakpoint.cl: Clear breakpoints.q: Quit the debugger.
Example Session
(Pdb) break 2
```
Breakpoint 1 at program.py:2
(Pdb) continue
> program.py(2)divide()
-> result = first / second
(Pdb) p first
20
(Pdb) p second
4
(Pdb) next
(Pdb) p result
5.0
(Pdb) continue
5.0
34.11 VS Code Debugger
```Visual Studio Code includes a graphical debugging interface for Python when the Python extension is installed. It allows breakpoints to be added by clicking beside line numbers.
During debugging, VS Code can display local variables, watched expressions, active breakpoints, the call stack, and a debug console.
Basic Steps
- Open the Python file in VS Code.
- Install or enable the Python extension.
- Select the correct Python interpreter.
- Click beside a line number to create a breakpoint.
- Open the Run and Debug panel.
- Select Python File as the debug configuration.
- Start debugging.
- Inspect variables after execution pauses.
- Use Step Over, Step Into, Step Out, or Continue.
Example Program
def calculate_total(
prices
```
):
total = 0
```
for price in prices:
total += price
return total
```
products = [
19.99,
25.50,
10.00
]
result = calculate_total(products)
print("Total:", result)
```
Output
Total: 55.49
Place a breakpoint on total += price. Each time the loop pauses, inspect price and total to watch the calculation change.
34.12 Breakpoints
```A breakpoint tells the debugger to pause before executing a selected line. It allows you to examine the program at an exact location.
Good breakpoint locations include the start of a suspicious function, before a failing calculation, inside an important loop, or immediately before data changes.
Example
def calculate_average(scores):
total = sum(scores)
count = len(scores)
average = total / count
return average
```
scores = [80, 90, 100]
print(calculate_average(scores))
```
Suggested Breakpoints
- On
total = sum(scores)to inspect the input list. - On
average = total / countto inspect both values. - On
return averageto verify the result.
Output
90.0
Breakpoint Advice
Avoid placing breakpoints on every line. Begin near the suspected problem and move the breakpoint as you learn more about the program.
```34.13 Conditional Breakpoints
```A conditional breakpoint pauses only when a specified expression becomes true. It is useful when a loop runs many times but the problem occurs only for one record or value.
Instead of repeatedly pressing Continue, you can set a condition such as score < 0, index == 500, or student["name"] == "Sara".
Example
scores = [
80,
92,
75,
-5,
88
```
]
for index, score in enumerate(scores):
print(
index,
score
)
```
Conditional Breakpoint
score < 0
Place the breakpoint on the print() line and use the condition above. The debugger pauses only when score is negative.
Output
0 80
```
1 92
2 75
3 -5
4 88
```
Useful Conditions
index == 100total > 1000name == "Michael"item is Nonelen(items) == 0
34.14 Stepping Through Code
```Stepping lets you execute a paused program in small parts. It helps reveal the exact line where a value becomes incorrect.
Step Over executes the current line without entering called functions. Step Into enters a called function. Step Out completes the current function and returns to its caller.
Example
def apply_discount(
price,
rate
```
):
discount = price * rate
return price - discount
def create_total(
price,
quantity
):
subtotal = price * quantity
total = apply_discount(
subtotal,
0.10
)
```
return total
```
print(create_total(20, 3))
```
Output
54.0
How to Step Through It
- Pause at
subtotal = price * quantity. - Use Step Over to calculate the subtotal.
- Inspect
subtotal. - Use Step Into on the
apply_discount()call. - Inspect
price,rate, anddiscount. - Use Step Out to return to
create_total(). - Inspect the final
total.
34.15 Inspecting Variables
```Inspecting variables means examining their current values and types while a program is paused. You can inspect simple values, lists, dictionaries, objects, function arguments, and nested data.
Do not inspect only the variable that fails. Also inspect the values used to create it. The problem may have started earlier in the program.
Example
order = {
"price": "25.00",
"quantity": 3,
"tax_rate": 0.13
```
}
subtotal = (
order["price"]
* order["quantity"]
)
print(subtotal)
```
Unexpected Output
25.0025.0025.00
Variables to Inspect
order
```
order["price"]
type(order["price"])
order["quantity"]
type(order["quantity"])
```
Corrected Example
order = {
"price": "25.00",
"quantity": 3,
"tax_rate": 0.13
```
}
price = float(
order["price"]
)
subtotal = (
price
* order["quantity"]
)
print(subtotal)
```
Correct Output
75.0
```
34.16 Call Stacks
```The call stack records the active chain of function calls. Each active function has a stack frame containing its local variables, arguments, and current execution position.
When one function calls another, a new frame is added. When the called function returns, its frame is removed. Debuggers allow you to move between frames and inspect variables from different levels.
Example
def calculate_tax(
subtotal
```
):
tax_rate = 0.13
return subtotal * tax_rate
def calculate_total(
subtotal
):
tax = calculate_tax(subtotal)
return subtotal + tax
def process_order(
price,
quantity
):
subtotal = price * quantity
return calculate_total(subtotal)
print(process_order(20, 3))
```
Output
67.8
Call Stack While Inside calculate_tax()
<module>
```
process_order()
calculate_total()
calculate_tax()
```
Variables by Frame
process_order():price,quantity, andsubtotalcalculate_total():subtotalandtaxcalculate_tax():subtotalandtax_rate
34.17 Debugging Exceptions
```Exception handling can prevent a program from stopping, but overly broad exception handling can hide useful debugging information. Catch only exceptions you can handle meaningfully.
While debugging, examine the exception type, message, input values, and traceback. Avoid using an empty except block because it hides the problem.
Poor Exception Handling
try:
number = int("Python")
```
except:
pass
print("Program finished.")
```
Output
Program finished.
The problem is hidden completely. A developer receives no useful information.
Better Exception Handling
value = "Python"
```
try:
number = int(value)
except ValueError as error:
print(
"Could not convert value:",
value
)
```
print(
"Error:",
error
)
Output
Could not convert value: Python
```
Error: invalid literal for int() with base 10: 'Python'
```
Preserving the Original Exception
def parse_age(value):
try:
return int(value)
except ValueError as error:
raise ValueError(
f"Invalid age value: {value}"
) from error
```
parse_age("unknown")
```
Result
The new exception explains the program-specific problem while preserving the original conversion error as its cause.
```34.18 Remote Debugging Concepts
```Remote debugging means controlling a program running in another process, computer, virtual machine, container, or server from a local debugging interface.
A debugging service usually listens for a debugger connection or connects back to the development environment. Because a debugger may provide extensive program access, remote debugging must be secured and should not be exposed publicly.
Common Remote Debugging Situations
- A Python application runs inside a development container.
- A web application runs on another development computer.
- A background worker runs in a separate process.
- A program behaves differently on a testing server.
- A cloud development environment hosts the application.
Conceptual Flow
Local editor
|
| Secure debugger connection
|
```
Remote Python process
|
| Breakpoints, variables, stack frames
|
Running application
```
Important Safety Practices
- Use remote debugging only in controlled development environments.
- Do not expose debugger ports directly to the public internet.
- Use authentication, secure networking, or trusted tunnels.
- Remove debugging configuration from production deployments.
- Avoid displaying passwords, tokens, and private customer data.
34.19 Rubber Duck Debugging
```Rubber duck debugging is a method where you explain your code line by line to another person, an object, or yourself. The listener does not need to understand programming.
Explaining the code forces you to state what each line should do, what values should exist, and why each decision is correct. The difference between what you say and what the code actually does often reveals the bug.
Example
numbers = [1, 2, 3, 4]
```
total = 0
for number in numbers:
total =+ number
print(total)
```
Incorrect Output
4
Explain It Line by Line
- Create a list containing four numbers.
- Start the total at zero.
- Visit each number.
- Add the number to the existing total.
- Display the completed total.
While explaining step four, you may notice that =+ assigns a positive value. It does not mean add and assign. The intended operator is +=.
Corrected Program
numbers = [1, 2, 3, 4]
```
total = 0
for number in numbers:
total += number
print(total)
```
Correct Output
10
```
34.20 Debugging Strategies
```Effective debugging uses a repeatable process instead of random code changes. Begin by reproducing the problem consistently and recording the exact input, output, and error information.
Reduce the problem to the smallest failing example, form one hypothesis, test it, and observe the result. Change one important thing at a time so you know which correction affected the behavior.
Recommended Process
- Reproduce the problem.
- Read the complete error message.
- Identify the smallest failing input.
- Determine the expected result.
- Inspect values near the failure.
- Check assumptions about types and ranges.
- Add a breakpoint or focused debug output.
- Form one clear explanation for the failure.
- Test that explanation.
- Apply the smallest appropriate correction.
- Run the original failing case again.
- Test normal, empty, minimum, maximum, and invalid inputs.
- Remove temporary debugging code.
- Add a test that prevents the bug from returning.
Binary Search Debugging
In a large program, place a check around the middle of the execution path. Determine whether the values are already incorrect at that point. Continue narrowing the suspicious area until the exact line or function is identified.
Useful Questions
- What did I expect?
- What actually happened?
- What is the smallest input that reproduces it?
- Which value first becomes incorrect?
- What assumption did the code make?
- Is the data type correct?
- Could the collection be empty?
- Could a value be
None? - Is the condition order correct?
- Does the loop include every intended item?
34.21 Chapter Practice Exercises
```The following exercises contain common debugging situations. For each exercise, identify the bug category, explain the cause, correct the code, and test the corrected version with more than one input.
- Correct a missing colon after an
ifstatement. - Correct an unterminated string.
- Correct inconsistent indentation.
- Correct a misspelled variable name causing
NameError. - Prevent division by zero.
- Prevent access to an invalid list index.
- Handle a missing dictionary key.
- Convert user input before arithmetic.
- Correct an incorrect tax formula.
- Correct an incorrectly ordered grade condition.
- Correct an off-by-one loop error.
- Use print statements to trace a changing total.
- Use
breakpoint()inside a function. - Run a script with
pdb. - Use
nextandstepin pdb. - Set a graphical breakpoint in VS Code.
- Create a conditional breakpoint for a negative value.
- Inspect a list and its current loop item.
- Inspect a dictionary containing an incorrect data type.
- Move between frames in a call stack.
- Catch a specific exception instead of every exception.
- Preserve an original exception with
raise ... from. - Use rubber duck debugging on a faulty loop.
- Reduce a large failing program to a small example.
- Write down expected and actual values.
- Test a function with an empty list.
- Test a function with minimum and maximum values.
- Add an assertion that checks an important assumption.
- Replace temporary debugging prints with logging.
- Create a regression test for a corrected bug.
- Debug a student average calculator.
- Debug a shopping-cart total calculator.
- Debug a password validator.
- Debug a file-reading program.
- Debug a nested function traceback.
- Debug a loop that skips the final item.
- Debug a function returning the wrong type.
- Debug a program that modifies the wrong variable.
- Debug a dictionary lookup with unexpected capitalization.
- Build a complete debugging report for a faulty program.
Practice Example
def calculate_average(scores):
total = 0
for score in scores:
total = score
return total / len(scores)
```
student_scores = [
80,
90,
100
]
print(
calculate_average(
student_scores
)
)
```
Incorrect Output
33.333333333333336
Problems
- The loop replaces the total instead of adding to it.
- An empty list would cause division by zero.
Corrected Version
def calculate_average(scores):
if not scores:
return 0.0
total = 0
for score in scores:
total += score
return total / len(scores)
```
student_scores = [
80,
90,
100
]
print(
calculate_average(
student_scores
)
)
print(
calculate_average([])
)
```
Correct Output
90.0
```
0.0
34.22 Chapter Debugging Project
```This project provides a faulty shopping-cart program containing syntax, runtime, and logic problems. The goal is to debug the application systematically instead of immediately replacing the entire program.
Faulty Shopping-Cart Program
products = {
"pizza": 14.99,
"burger": 9.99,
"drink": 2.50
```
}
cart = [
{
"name": "pizza",
"quantity": 2
},
{
"name": "burger",
"quantity": "3"
},
{
"name": "salad",
"quantity": 1
}
]
def calculate_subtotal(cart, products)
subtotal = 0
```
for item in cart:
name = item["name"]
quantity = item["quantity"]
price = products[name]
item_total = price + quantity
subtotal = item_total
return subtotal
```
def calculate_tax(subtotal):
return subtotal * 13
def calculate_delivery(subtotal):
if subtotal > 40:
return 5
```
return 0
```
subtotal = calculate_subtotal(
cart,
products
)
tax = calculate_tax(subtotal)
delivery = calculate_delivery(subtotal)
total = subtotal + tax + delivery
print("Subtotal:", subtotal)
print("Tax:", tax)
print("Delivery:", delivery)
print("Total:", total)
```
Problems to Find
- The function definition is missing a colon.
- One quantity is stored as a string.
- The cart contains a product missing from the product dictionary.
- Item totals use addition instead of multiplication.
- The subtotal is replaced rather than accumulated.
- The tax formula uses 13 instead of 0.13.
- The delivery rule may be reversed from the intended requirement.
- Money values are not rounded for display.
Debugging Version with Focused Output
products = {
"pizza": 14.99,
"burger": 9.99,
"drink": 2.50
```
}
cart = [
{
"name": "pizza",
"quantity": 2
},
{
"name": "burger",
"quantity": "3"
},
{
"name": "salad",
"quantity": 1
}
]
def calculate_subtotal(
cart,
products
):
subtotal = 0
```
for index, item in enumerate(cart):
print()
print("Processing index:", index)
print("Item:", item)
name = item["name"]
quantity = item["quantity"]
print("Name:", name)
print("Quantity:", quantity)
print(
"Quantity type:",
type(quantity)
)
if name not in products:
print(
"Unknown product:",
name
)
continue
try:
quantity = int(quantity)
except ValueError:
print(
"Invalid quantity:",
quantity
)
continue
price = products[name]
item_total = (
price
* quantity
)
print("Price:", price)
print(
"Item total:",
item_total
)
subtotal += item_total
print(
"Running subtotal:",
subtotal
)
return subtotal
```
subtotal = calculate_subtotal(
cart,
products
)
print()
print("Debug subtotal:", subtotal)
```
Debug Output
Processing index: 0
```
Item: {'name': 'pizza', 'quantity': 2}
Name: pizza
Quantity: 2
Quantity type:
Price: 14.99
Item total: 29.98
Running subtotal: 29.98
Processing index: 1
Item: {'name': 'burger', 'quantity': '3'}
Name: burger
Quantity: 3
Quantity type:
Price: 9.99
Item total: 29.97
Running subtotal: 59.95
Processing index: 2
Item: {'name': 'salad', 'quantity': 1}
Name: salad
Quantity: 1
Quantity type:
Unknown product: salad
Debug subtotal: 59.95
```
Fully Corrected Shopping-Cart Program
from dataclasses import dataclass
```
from decimal import (
Decimal,
InvalidOperation,
ROUND_HALF_UP
)
MONEY_UNIT = Decimal("0.01")
TAX_RATE = Decimal("0.13")
FREE_DELIVERY_MINIMUM = Decimal("40.00")
DELIVERY_CHARGE = Decimal("5.00")
@dataclass
class CartItem:
name: str
quantity: int
PRODUCTS: dict[str, Decimal] = {
"pizza": Decimal("14.99"),
"burger": Decimal("9.99"),
"drink": Decimal("2.50")
}
RAW_CART = [
{
"name": "pizza",
"quantity": 2
},
{
"name": "burger",
"quantity": "3"
},
{
"name": "salad",
"quantity": 1
}
]
def round_money(
amount: Decimal
) -> Decimal:
return amount.quantize(
MONEY_UNIT,
rounding=ROUND_HALF_UP
)
def parse_quantity(
value: object
) -> int:
try:
quantity = int(value)
```
except (
TypeError,
ValueError
) as error:
raise ValueError(
f"Invalid quantity: {value}"
) from error
if quantity <= 0:
raise ValueError(
"Quantity must be greater than zero."
)
return quantity
```
def build_cart(
raw_cart: list[dict[str, object]],
products: dict[str, Decimal]
) -> list[CartItem]:
valid_items: list[CartItem] = []
```
for index, raw_item in enumerate(
raw_cart,
start=1
):
raw_name = raw_item.get("name")
if not isinstance(raw_name, str):
print(
f"Skipped item {index}: "
"invalid product name."
)
continue
name = raw_name.strip().lower()
if name not in products:
print(
f"Skipped item {index}: "
f"unknown product '{name}'."
)
continue
try:
quantity = parse_quantity(
raw_item.get("quantity")
)
except ValueError as error:
print(
f"Skipped item {index}:",
error
)
continue
valid_items.append(
CartItem(
name=name,
quantity=quantity
)
)
return valid_items
```
def calculate_subtotal(
cart: list[CartItem],
products: dict[str, Decimal]
) -> Decimal:
subtotal = Decimal("0.00")
```
for item in cart:
price = products[item.name]
item_total = (
price
* item.quantity
)
subtotal += item_total
return round_money(subtotal)
```
def calculate_tax(
subtotal: Decimal
) -> Decimal:
return round_money(
subtotal * TAX_RATE
)
def calculate_delivery(
subtotal: Decimal
) -> Decimal:
if subtotal >= FREE_DELIVERY_MINIMUM:
return Decimal("0.00")
```
return DELIVERY_CHARGE
```
def display_receipt(
cart: list[CartItem],
products: dict[str, Decimal]
) -> None:
subtotal = calculate_subtotal(
cart,
products
)
```
tax = calculate_tax(subtotal)
delivery = calculate_delivery(
subtotal
)
total = round_money(
subtotal
+ tax
+ delivery
)
print()
print("SHOPPING CART RECEIPT")
print("=" * 45)
for item in cart:
price = products[item.name]
item_total = round_money(
price * item.quantity
)
print(
f"{item.name.title():<15} "
f"{item.quantity:>3} "
f"x ${price:>6} "
f"= ${item_total:>7}"
)
print("-" * 45)
print(
f"{'Subtotal':<30} "
f"${subtotal:>7}"
)
print(
f"{'Tax':<30} "
f"${tax:>7}"
)
print(
f"{'Delivery':<30} "
f"${delivery:>7}"
)
print(
f"{'Total':<30} "
f"${total:>7}"
)
```
def main() -> None:
cart = build_cart(
RAW_CART,
PRODUCTS
)
```
if not cart:
print(
"The cart contains no valid items."
)
return
display_receipt(
cart,
PRODUCTS
)
```
if **name** == "**main**":
main()
```
Output
Skipped item 3: unknown product 'salad'.
```
# SHOPPING CART RECEIPT
Pizza 2 x $ 14.99 = $ 29.98
Burger 3 x $ 9.99 = $ 29.97
---------------------------------------
Subtotal $ 59.95
Tax $ 7.79
Delivery $ 0.00
Total $ 67.74
```
Project Explanation
The corrected program validates each raw cart item before calculation. Product names are cleaned and checked against the available product dictionary. Quantities are converted to integers and must be greater than zero.
Invalid items are skipped with useful messages instead of causing the complete application to fail. Known products are converted into structured CartItem objects.
Item totals use multiplication, and the subtotal uses += so every valid item contributes to the final amount. The tax rate is represented as 0.13.
The delivery charge becomes zero when the subtotal reaches the free-delivery minimum. Decimal arithmetic and explicit rounding are used for money calculations.
How to Run the Project
- Create a file named
debugging_project.py. - Copy the faulty version into the file first.
- Run it and record the first error.
- Correct only the syntax problem.
- Run it again and inspect the next problem.
- Add focused debugging output.
- Set breakpoints inside the cart loop.
- Inspect the product name, quantity, price, item total, and subtotal.
- Replace the faulty program with the corrected version.
- Run
python debugging_project.py. - On some computers, run
python3 debugging_project.py.
Project Challenges
- Add a discount code and debug its calculation.
- Add pickup and delivery options.
- Add duplicate products and combine their quantities.
- Add a conditional breakpoint for quantities above ten.
- Add an invalid negative quantity and trace it.
- Write skipped-item messages to a log file.
- Load the cart from a JSON file.
- Handle a missing JSON file.
- Handle malformed JSON data.
- Create automated tests for every corrected bug.
- Create a regression test for the tax calculation.
- Create a regression test for free delivery.
- Use pdb to inspect the subtotal calculation.
- Use the VS Code call stack while inside
round_money(). - Write a debugging report describing each bug and correction.
A modern course built to help learners study step by step with clarity, comfort, and confidence.