◐ ai-teacher

🐍 Python for Data Engineers · Chapter 5

Errors, files and imports

Read the last line of the traceback first. Then read the line above it. That habit alone is worth a month of guessing.

Study this lesson free in the app →

The basics

An exception is Python's way of saying "I can't continue": it stops normal flow and, unless something catches it, prints a traceback and ends the program. Files are how data gets in and out; imports are how one file uses code from another.

The problem it solves: real data is dirty and real environments break. A missing key, a CSV that is actually JSON, a package installed in the wrong place. You need to read the error quickly, handle the cases you expect, and make sure the ones you don't expect are loud, not silent.

The mental model: a smoke alarm. The exception isn't the fire; it's what stops you sleeping through it. except: pass is taking the battery out. Catch the specific alarm you planned for, and if you catch it, fix it or record it.

Words you must know 5

Traceback
the printed call stack. Read it bottom-up: last line = what went wrong, the line above = where.
try / except
run code, and handle named exception types (KeyError, ValueError, TypeError) if they're raised.
finally
runs on every exit path: success, error, or return. Used for cleanup.
Context manager
with open(...) as fh: guarantees the file is closed, even on an error.
Module
a .py file you can import. Never name your own file json.py or random.py; it hides the real library.

Explained

Traceback (most recent call last):
  File "pipeline.py", line 42, in <module>
    total = fare_of(event)
  File "pipeline.py", line 18, in fare_of
    return float(event["fare"])
KeyError: 'fare'

Beginners see a wall of text and panic. There are only two facts here:

What went wrong — the last line: no key called fare. Where — the line just above it: pipeline.py, line 18.

Read bottom-up. The stack is printed oldest-first, but the useful part is at the end.

The smoke alarm

An exception is a smoke alarm. It's not the fire — it's the thing that stops you sleeping through the fire.

Catching everything and ignoring it — except: pass — is taking the battery out. The house still burns; you just find out later, from the insurance company.

Catching the specific alarm you planned for — "the toaster sets this off every morning, that one I handle" — is correct.

And the one rule that survives everything: if you catch it, either fix it or record it. Silence is the only genuinely wrong answer.

try / except, done properly

# ✗ the battery-removal version
try:
    fare = float(event["fare"])
except:
    fare = 0                # what failed? nobody will ever know

# ✓ specific, and it leaves evidence
try:
    fare = float(event["fare"])
except KeyError:
    log.warning("event %s has no fare", event["id"])
    return None
except ValueError:
    log.warning("event %s has unparseable fare %r", event["id"], event["fare"])
    return None

# the full shape
try:
    data = json.loads(line)
except json.JSONDecodeError as err:
    rejected.append((line, str(err)))    # keep the evidence
else:
    process(data)                        # runs only if no exception
finally:
    counter += 1                         # runs no matter what

The exceptions worth knowing by name: KeyError (missing dict key), ValueError (right type, wrong value), TypeError (wrong type entirely), FileNotFoundError, ZeroDivisionError, AttributeError (usually a typo, or a None you didn't expect).

Watch control jump

finally is how you guarantee cleanup — closing a connection, releasing a lock — no matter which way the function exits.

Files: always `with`

import json
from pathlib import Path

# `with` closes the file even if the loop raises
with open("rides.jsonl") as fh:
    for line in fh:                    # streams — one line at a time
        event = json.loads(line)

# writing
with open("out.jsonl", "w") as fh:    # "w" overwrites, "a" appends
    for e in events:
        fh.write(json.dumps(e) + "\n")

# whole-file JSON (only when it's small)
config = json.loads(Path("config.json").read_text())

# paths, portably
raw = Path("data") / "raw" / "rides.jsonl"
raw.parent.mkdir(parents=True, exist_ok=True)
raw.exists()

Two habits that prevent most file bugs: use with so files always close, and iterate the handle (for line in fh) rather than .read(), so a 12 GB file doesn't become a 12 GB variable.

Imports: where code comes from

import json                       # the whole module → json.loads()
from pathlib import Path          # one name → Path()
from datetime import datetime as dt   # renamed

from .validate import validate    # a module in the same package

Python finds modules by searching, in order: the current directory, then installed packages. Which explains the two errors everyone hits:

And the trap that eats an afternoon: never name your file after a library. A local json.py or random.py shadows the real one, and the error message points anywhere but at your filename.

Why does this pipeline report success?

total = 0
for line in open("rides.jsonl"):
    try:
        event = json.loads(line)
        total += event["fare"]
    except:
        pass

print(f"total: {total}")

The file has 100,000 rides. It prints total: 0 and exits 0.

Gotchas

Words to know

ExceptionAn error object raised when something goes wrong, interrupting normal flow.
TracebackThe printed call stack showing where an exception came from; read bottom-up.
KeyErrorA dict was asked for a key it doesn't have.
ValueErrorRight type, impossible value — e.g. int("abc").
TypeErrorAn operation applied to the wrong type entirely.
finallyA block that runs on every exit path — success, exception, or return.
Context managerThe with protocol; guarantees cleanup such as closing a file.
ModuleA .py file whose names you can import.
Circular importTwo modules importing each other, so neither can finish loading.

In two minutes

Test yourself

1. Which line of a traceback tells you WHAT went wrong?

  1. the first
  2. the last
  3. the middle
  4. the file path
Show the answer

the last — The final line names the exception type and message. The line just above it tells you where. Read bottom-up.

2. Why is except: pass so dangerous?

  1. It is slow
  2. It hides every possible cause and even blocks Ctrl-C
  3. It only catches KeyError
  4. It always re-raises
Show the answer

It hides every possible cause and even blocks Ctrl-C — It discards the reason, so a 100% failure looks like a clean run. A bare except also swallows KeyboardInterrupt and SystemExit.

3. What does with open(...) guarantee?

  1. The file is read faster
  2. The file is closed even if an exception is raised
  3. The file is locked
  4. The file is loaded into memory
Show the answer

The file is closed even if an exception is raised — It's a context manager — cleanup happens on every exit path, which is why you never need an explicit .close().

4. You get ModuleNotFoundError for a package you 'definitely installed'. Most likely?

  1. The package is broken
  2. It's installed in a different environment than the one running your code
  3. Python is too old
  4. You need to restart the machine
Show the answer

It's installed in a different environment than the one running your code — Nearly always an unactivated venv or a different interpreter. Check which python and pip list from the same shell.

5. Why should a job with 100% row rejection NOT exit 0?

  1. Exit codes are cosmetic
  2. Because the scheduler marks it successful and empty data reaches dashboards as fact
  3. It wastes CPU
  4. It corrupts the file
Show the answer

Because the scheduler marks it successful and empty data reaches dashboards as fact — Silent partial or empty success is the worst failure mode in data work — someone makes a decision on a number nobody flagged.

6. int("12.5") raises which exception?

  1. TypeError
  2. KeyError
  3. ValueError
  4. SyntaxError
Show the answer

ValueError — The argument is the right type (a string) but an impossible value for int(). TypeError is for the wrong type altogether, like '5' + 1.

There are 11 questions, 3 puzzles and 6 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.

Learn it so it stays. Read it, test yourself, then recall it on two different days. The app schedules that for you, free.

Start this lesson →