1.4.12Python & Scientific Computing

Reading documentation and debugging

3,100 words14 min readdifficulty · medium

Overview

Documentation reading and debugging are meta-skills that separate self-sufficient programmers from those who get stuck. You'll spend more time reading docs and fixing bugs than writing new code—mastering these skills multiplies your productivity.

Figure — Reading documentation and debugging

Without these skills, you're memorizing recipes instead of learning to cook.


Part 1: Reading Documentation Like a Pro

The goal isn't to memorize—it's to extract the mental model the library author has.

The80/20 Documentation Reading Strategy

20% of documentation contains 80% of what you need. Focus on:

  1. Function signature line (name, params, return type)
  2. First paragraph (one-sentence purpose)
  3. Parameters section (what each does, types, defaults)
  4. Returns section (shape, type, edge cases)
  5. Examples section (usage patterns)

Step-by-step extraction:

  • Signature: 2 required args (start, stop), 4 optional
  • Purpose: "Return evenly spaced numbers over a specified interval" → Creates arithmetic sequence, not just range()
  • Key params:
    • num=50: How many points (default 50)
    • endpoint=True: Include stop value (vs. range() which excludes)
    • retstep=False: Optionally return spacing ∆x
  • Returns: ndarray (if retstep=False), or tuple (array, step)
  • Mental model: Unlike range(), this includes endpoint and works with floats

Why this step? The signature + purpose tell you when to use this vs alternatives (range, arange, geomspace). The parameters show control knobs for behavior.

Bad approach: Google "python split keep delimiter" → copyStackOverflow → hope it works

Good approach:

  1. Start broad: Python string methods docs
  2. Scan method names: split, partition, rsplit, splitlines...
  3. Check partition: "Split at first occurrence, return 3-tuple (before, sep, after)" → Keeps delimiter! But only first occurrence.
  4. Check re.split: "Split by regex, maxsplit optional" → Parameter re.split(pattern, string, maxsplit=0, flags=0) → But loses delimiters by default...
  5. Read further: "Capturing groups in pattern are also returned" → re.split(r'(\d+)', 'a1b2c')['a', '1', 'b', '2', 'c']

Why this step? You build a decision tree of when to use each variant, not just "split works".

Common Documentation Patterns

WHY this formula?

  • Purpose: When to reach for this tool
  • Contracts: What you must provide, what you'll get back
  • Edge cases: Where it breaks or behaves surprisingly
  • Alternatives: Why this over similar functions

HOW to apply:

  1. Read the one-line purpose → answer "What problem does this solve?"
  2. Check parameters → answer "What do I control?"
  3. Check returns → answer "What shape/type do I get back?"
  4. Read examples → answer "What's the canonical usage?"
  5. Scan 'See Also' → answer "What else could I use instead?"

Mental model extraction:

  • Purpose: "Group DataFrame by values in column(s), then apply aggregate functions"
  • Contracts:
    • Input: Column name(s) or function to define groups
    • Output: DataFrameGroupBy object (lazy, must call .mean()/.sum()/etc.)
  • Key parameter: as_index=True → group keys become index (vs column)
  • Edge case: Groups are created only for values present in the data—there's no notion of "empty" groups. However, dropna=True (default) means rows with NaN in the grouping key are excluded entirely; set dropna=False to keep them as their own group. (With categorical grouping keys, the observed parameter controls whether unused categories appear.)
  • Alternative: pivot_table for crosstab-style aggregation

Why this step? Now you know groupby returns a lazy object (you must aggregate), that as_index controls output structure, and that dropna decides whether NaN keys survive—three common confusion points.


Part 2: Systematic Debugging

The rookie mistake: randomly changing code hoping it works. The pro move: narrow the search space systematically.

The Binary Search Debugging Strategy

Derivation from first principles:

If your program has NN lines and one is wrong, random search takes O(N)O(N) checks on average. But if you test the middle, you eliminate half the code:

T(N)=1+T(N/2)T(N)=O(logN)T(N) = 1 + T(N/2) \Rightarrow T(N) = O(\log N)

HOW to apply:

  1. Identify the bad output location (line X produces wrong value)
  2. Identify the last good input (data was correct at line Y)
  3. Check the middle line Z between Y and X
  4. If Z is wrong → bug is between Y and Z
  5. If Z is correct → bug is between Z and X
  6. Recurse

Bug: Returns 0.0 for students = [{'name': 'Alice', 'score': 85}, ...]


**Symptom**: Expected ~85, got 0.0

**Hypothesis 1**: Division by zero?
**Test**: `print(len(students))` → outputs 1, not 0. Hypothesis refuted.

**Hypothesis 2**: `total` is 0 when it shouldn't be?
**Test**: `print(total)` before division → outputs 0. Confirmed! Bug is in accumulation.

**Hypothesis 3**: Loop never runs?
**Test**: `print("Loop running")` inside loop → prints. Loop runs, but doesn't add.

**Hypothesis 4**: `student['score']` doesn't exist or is wrong type?
**Test**: `print(student, type(student['score']))` → `{'name': 'Alice', 'score': 85} <class 'int'>`. Data is fine.

**AHA moment**: Check `total` initialization... it's inside the loop!
```python
for student in students:
    total = 0  # BUG: Resets to 0 every iteration!
    total += student['score']

Why this step matters: Each test eliminates half the possibility space. Without systematic narrowing, you'd thrash between random changes.

The Stack Trace Reading Skill

Read bottom-to-top:

  1. Exception type (last line): KeyError, TypeError, AttributeError, etc.
  2. Error message: The specific value/name that failed
  3. Immediate location: File and line where exception raised
  4. Call stack: How you got there (sequence of function calls)

Bottom-up analysis:

  • Exception: KeyError: 'price' → Dictionary missing'price' key
  • Immediate cause: Line 23 in normalize() tries record['price']
  • How we got there:
    • main.py:45 called process_data(raw_data)
    • Which called clean_records(data) at line 12
    • Which list-comprehended normalize(r) at line 67
    • Which crashed on a specific record

Hypothesis: One record in raw_data doesn't have a 'price' field.

Why this step? The stack trace tells you exactly where (line 23) and why (missing key), plus how (the call chain). Don't ignore this free information!

The Print-Driven Debugging Pattern

When to use: Complex data transformations, unclear intermediate state

HOW:

# BAD: One giant chain
result = df.groupby('category').apply(lambda x: x.sort_values('date').head(3)).reset_index(drop=True)
 
# GOOD: Break into steps, print each
step1 = df.groupby('category')
print("After groupby:", step1.groups.keys())
 
step2 = step1.apply(lambda x: x.sort_values('date').head(3))
print("After apply:\n", step2.head())
 
step3 = step2.reset_index(drop=True)
print("Final result:\n", step3)

Why this works: You observe the data transformation at each stage, catching where it diverges from expectation.

Goal: Add [1,2,3] to each row of a 4x3 matrix

matrix = np.array(10, 20, 30, [40, 50, 60], [70, 80, 90], [100, 110, 120]])

offset = np.array([1, 2, 3])

result = matrix + offset print(result)

Works! 11, 22, 33, [41, 52, 63], ...]

Now try to add [1, 2, 3, 4] to each column (different offset per row)

offset_col = np.array([1, 2, 3, 4]) result2 = matrix + offset_col # ValueError: shape (4,3) + (4,) can't broadcast


**Debug process**:
1. **Print shapes**: `print(matrix.shape, offset_col.shape)` → `(4, 3)` and `(4,)`
2. **Understand broadcasting rule**: Dimensions align right-to-left. 
   - `(4, 3) + (3,)` works: last dims match (3 == 3)
   - `(4, 3) + (4,)` fails: last dims don't match (3 ≠ 4)
3. **Fix**: Reshape offset to column vector `(4, 1)` so it broadcasts across columns:
   ```python
   offset_col = offset_col.reshape(-1, 1)  # Now (4, 1)
   result2 = matrix + offset_col  # Works! (4,3) + (4,1) → (4,3)

Why this step? Printing shapes reveals the geometric mismatch, guiding the fix.


Common Mistakes & How to Fix Them

Why it's wrong: You waste time debugging behavior that's documented. Example: Using list.sort() expecting it to return the sorted list, but it returns None (in-place sort). The docs say this immediately.

Fix: Skim the signature + parameters before writing code. Five minutes of reading saves an hour of debugging.

Why it's wrong: You're injecting new bugs faster than finding old ones. You lose track of what state the code is in.

Fix: Change one thing at a time and test. If it doesn't fix the bug, revert before trying the next hypothesis.

Why it's wrong: The error message tells you exactly what's wrong 80% of the time. KeyError: 'price' means the key 'price' doesn't exist—that's precise!

Fix: Read the last line first (exception + message), then trace up to find where in your code. Google the exception type + your context if unclear.

Why it's wrong: Assumptions are where bugs hide. Production data is mesy, users are creative, files get moved.

Fix: Add assert statements for critical assumptions:

def process_image(img_path):
    assert os.path.exists(img_path), f"Image not found: {img_path}"
    img = load_image(img_path)
    assert img.shape[2] == 3, f"Expected RGB, got shape {img.shape}"
    # ... rest of code

Active Recall Drills

Recall Feynman Explanation (Explain to a12-Year-Old)

Imagine you get a new video game but no tutorial. You could:

  1. Mash random buttons (debugging by guessing)
  2. Read the manual (documentation)
  3. Try systematically: Press A, see what happens. Press B, see what happens. (Scientific debugging)

Documentation is like the game manual—it tells you what each button should do. Debugging is when you press A and something unexpected happens. Instead of panicking, you:

  • Check the manual: "Did I read the A button description wrong?"
  • Test one thing: "Does A work in the menu? In battle? Only sometimes?"
  • Narrow it down: "It breaks after I pick up the red item, so the bug must be in the item code."

The trick is: change only one thing and test. If you mash all the buttons at once, you'll never know which one caused the problem!


Connections

  • 1.4.1-Python-basics-syntax-and-variables – Where you first encounter Python errors
  • 1.4.5-File-IOand-CSV-handling – Common source of FileNotFoundError, PermissionError
  • 1.4.10-Introduction-to-matplotlib – Reading docs for plotting parameters
  • 1.7.2-Data-preprocessing-and-cleaning – Debugging mesy data pipelines
  • 2.3.4-Hyperparameter-tuning-grid-search-random-search – Debugging model performance (is it bug or bad hyperparameters?)

#flashcards/ai-ml

What are the four key pieces of information documentation provides? :: API signature, Contracts (pre/postconditions), Edge cases, Examples

What is the 80/20 rule for reading documentation?
Focus on: function signature, first paragraph, parameters section, returns section, and examples—these contain 80% of useful info
What are the 5 steps of the scientific debugging method?
1. Observe symptom, 2. Form hypothesis, 3. Design experiment, 4. Run experiment, 5. Repeat until root cause found
Why does binary search debugging take O(log N) time instead of O(N)?
Each test eliminates half the remaining code, so you need log₂(N) tests to narrow down to one line
How should you read a Python stack trace?
Bottom-top: 1. Exception type (last line), 2. Error message, 3. Immediate location (line number), 4. Call stack (how you got there)
What's wrong with debugging by guessing (random code changes)?
You inject new bugs faster than finding old ones, lose track of code state, and waste time on non-systematic exploration
What should you do before trying to fix a bug?
Read the error message fully (it tells you exactly what's wrong 80% of the time), then form a hypothesis about the root cause
What is the print-driven debugging pattern?
Break complex operations into steps, print intermediate state after each step, catch where data diverges from expectation
Why should you read documentation before writing code, not after it breaks?
Five minutes of reading saves an hour of debugging behavior that's already documented (e.g., list.sort() returns None)
In pandas groupby, what does the dropna parameter control?
Whether rows with NaN in the grouping key are excluded (dropna=True, default) or kept as their own group (dropna=False). groupby only creates groups for values actually present in the data.
What's the fix for "debugging by guessing"?
Change one thing at a time and test. If it doesn't work, revert before trying the next hypothesis. Keep a clear mental model of code state.

Concept Map

specifies

specifies

specifies

specifies

extract

focuses

finds right function in

guides

when reality differs

systematic method

prompts re-reading

connects

connects

connects

Read Documentation

Write Code

Debug

Mental Model

API Signature

Contracts

Edge Cases

Examples

80/20 Strategy

Search Strategy

Feedback Loop

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, jab hum programming karte hain to sabse important cheez ye samajhna hai ki naya code likhne se zyada time hum documentation padhne aur bugs fix karne me lagate hain. Ye do skills—docs reading aur debugging—hi asal me self-sufficient programmer banati hain. Documentation basically ek instruction manual hai un libraries ka jo aapne khud nahi likhi (jaise numpy, pandas). Aur debugging ek scientific method hai—hypothesis banao, test karo, refine karo. In dono ka ek feedback loop hai: docs padho taaki samajh aaye kya hona chahiye, code likho, phir jab reality expectation se match na kare to systematically dhundo ki galti kahan hai, aur phir docs dobara padho apna mental model theek karne ke liye. Iske bina aap sirf recipes ratt rahe ho, cooking sikh nahi rahe.

Ab documentation padhne ka ek smart tareeka hai—80/20 rule. Matlab 20% documentation me 80% kaam ki cheez hoti hai. Aapko poora manual ratna nahi hai, bas kuch key cheezon pe focus karo: function ka signature (naam, parameters, return type), pehla paragraph (ek line ka purpose), parameters section, returns section, aur examples. Jaise numpy.linspace ka example lo—signature dekh ke pata chala ki 2 required aur 4 optional arguments hain, purpose padh ke samjha ki ye evenly spaced numbers deta hai, aur endpoint=True jaise param se pata chala ki ye range() se alag hai kyunki ye stop value ko include karta hai aur floats ke saath kaam karta hai. Ye chhoti chhoti cheezein aapko batati hain ki kab kaunsa function use karna hai.

Sabse important intuition ye hai ki aap ek mental model extract kar rahe ho, ratt nahi rahe. Formula simple hai: Mental Model = Purpose + Contracts + Edge Cases + Alternatives. Purpose batata hai kab ye tool use karo, contracts batate hain kya dena hai aur kya milega, edge cases batate hain kahan ye tootega ya weird behave karega, aur alternatives batate hain ki similar functions me se ye kyun choose kiya. Jaise string split karne ka example—Google pe blindly search karke StackOverflow se copy karne ki jagah, agar aap Python ke string methods systematically scan karo to partition, re.split jaise options ka ek proper decision tree ban jaata hai. Yahi skill aapko regional student se ek confident programmer banati hai—aap har naya problem khud solve kar sakoge bina kisi pe depend kiye.

Go deeper — visual, from zero

Test yourself — Python & Scientific Computing

Connections