CC-BY-SA 4.0, Rolf Kranz
Understand what happens
Can be wrong
The grid to interpret your experiments
What is actually happening when you run your code
Used to test your hypotheses
Scientists use a research log
Debugging log is great to write post-mortems
Let's see how that applies to a toy example
I need to iterate on cumulative lists:
[0], [0, 1], [0, 1, 2], ...
I need to add a special value at the beginning of each list:
-100
def prepend(l: list, beginning: list = [-100]):
beginning.extend(l)
return beginning
for i in range(5):
zero_to_i = list(range(i))
l = prepend(zero_to_i)
print(len(l))
# Expected: 5; i = 4, zero_to_i = [0, 1, 2, 3], l = [-100, 0, 1, 2, 3]
# Actual: 11
prepend is adding -100 in front of the list.
def prepend(l: list, beginning: list = [-100]):
beginning.extend(l)
return beginning
print(prepend([0, 1, 2]))
# [-100, 0, 1, 2]
Let's print the values of zero_to_i
def prepend(l: list, beginning: list = [-100]):
beginning.extend(l)
return beginning
for i in range(5):
zero_to_i = list(range(i))
print(zero_to_i)
l = prepend(zero_to_i)
# []
# [0]
# [0, 1]
# [0, 1, 2]
# [0, 1, 2, 3]
Let's print the values of l
def prepend(l: list, beginning: list = [-100]):
beginning.extend(l)
return beginning
for i in range(5):
zero_to_i = list(range(i))
l = prepend(zero_to_i)
print(l)
# [-100]
# [-100, 0]
# [-100, 0, 0, 1]
# [-100, 0, 0, 1, 0, 1, 2]
# [-100, 0, 0, 1, 0, 1, 2, 0, 1, 2, 3]
prepend is always behaving in the same way.
def prepend(l: list, beginning: list = [-100]):
beginning.extend(l)
return beginning
print(prepend([0, 1, 2]))
# [-100, 0, 1, 2]
print(prepend([0, 1, 2]))
# [-100, 0, 1, 2, 0, 1, 2]
Investigate what is happening with multiple successive calls to prepend.
def prepend(l: list, beginning: list = [-100]):
print("l: ", l)
print("beginning: ", beginning)
beginning.extend(l)
return beginning
prepend([0, 1, 2])
# l: [0, 1, 2]
# beginning: [-100]
prepend([0, 1, 2])
# l: [0, 1, 2]
# beginning: [-100, 0, 1, 2]
def prepend(l: list, beginning: list | None = None):
if beginning is None:
beginning = [-100]
beginning.extend(l)
return beginning
for i in range(5):
zero_to_i = list(range(i))
l = prepend(zero_to_i)
print(len(l))
# Expected: 5
# Actual: 5
Rely on Occam's razor
Use documentation and architecture diagrams
But don't trust them
Follow the flow
Divide and conquer
Remember: at least one of your assumptions is wrong
Invest in tooling
Trust nothing / print everything
Divide and conquer
Inspect values at interfaces
Leverage temporality
Identify source of randomness
Debugging is a team effort
See also The Pocket Guide to Debugging, by Julia Evans