Yesterday's chain assumed every item succeeded. Real loops have partial failures — item 3 of 5 fails, but you still want items 1, 2, 4, and 5 done. Don't crash on the first failure; log it and keep going.
items = ["a", "b", "c", "d", "e"]
results = {"ok": 0, "fail": 0}
for item in items:
try:
if item == "c": # force item 3 to fail
raise RuntimeError("forced for demo")
# ... do real work for `item` here ...
results["ok"] += 1
except Exception as e:
results["fail"] += 1
print(f"item {item} failed: {type(e).__name__}: {e}")
print(f"summary: {results}")
# summary: {'ok': 4, 'fail': 1}The try/except is inside the loop. The exception is caught per-item, the counter increments, the loop continues. End-of-loop report tells you what happened.
When should I crash vs continue?
Two rules. Crash if the failure means the next item is meaningless — e.g., your auth token expired (every subsequent call will also fail; pointless to keep going). Continue if items are independent — one bad message shouldn't stop the rest.
The pattern: catch, log, count, continue. Then after the loop, decide if the failure rate is too high to call the run a success.
What about logging which items failed?
Print per-item, like the example. For more advanced cases, append failures to a list and print them all at the end. We'll do tagged structured logging tomorrow — same shape, more parseable output.
results = {"ok": 0, "fail": 0}
for item in items:
try:
do_work(item)
results["ok"] += 1
except Exception as e:
results["fail"] += 1
print(f"item {item} failed: {type(e).__name__}: {e}")
print(f"summary: {results}")Four-line skeleton. The try wraps only the per-item work, not the loop framing.
When each iteration's failure is independent — bad message data, individual item missing — continue. The loop's job is to do whatever it can.
When one failure indicates a systemic problem — auth expired, rate limit hit, service down — crash. The remaining iterations will all fail the same way; logging them adds noise.
A hybrid: count failures, abort if the rate exceeds a threshold:
results = {"ok": 0, "fail": 0}
for i, item in enumerate(items):
try:
do_work(item)
results["ok"] += 1
except Exception:
results["fail"] += 1
if results["fail"] > 3:
raise RuntimeError("too many failures — bailing")One line, parseable, includes both numbers:
summary: {'ok': 4, 'fail': 1}Future-you greps for summary: and reads exactly one line per script run. Compare across days: ok=4 fail=1 yesterday, ok=3 fail=2 today — something is degrading.
For cases where you want to retry just the failures later:
failed_items = []
for item in items:
try:
do_work(item)
except Exception as e:
failed_items.append({"item": item, "error": str(e)})
if failed_items:
print(f"to retry next run: {[f['item'] for f in failed_items]}")Now tomorrow's run can read failed_items and try just those, instead of the full input.
# wrong
for item in items:
try:
do_work(item)
except Exception:
pass # silent — you'll never know anything failedA caught error must be logged and counted. Silent swallowing is the worst outcome — the script reports success, but did nothing.
Yesterday's chain assumed every item succeeded. Real loops have partial failures — item 3 of 5 fails, but you still want items 1, 2, 4, and 5 done. Don't crash on the first failure; log it and keep going.
items = ["a", "b", "c", "d", "e"]
results = {"ok": 0, "fail": 0}
for item in items:
try:
if item == "c": # force item 3 to fail
raise RuntimeError("forced for demo")
# ... do real work for `item` here ...
results["ok"] += 1
except Exception as e:
results["fail"] += 1
print(f"item {item} failed: {type(e).__name__}: {e}")
print(f"summary: {results}")
# summary: {'ok': 4, 'fail': 1}The try/except is inside the loop. The exception is caught per-item, the counter increments, the loop continues. End-of-loop report tells you what happened.
When should I crash vs continue?
Two rules. Crash if the failure means the next item is meaningless — e.g., your auth token expired (every subsequent call will also fail; pointless to keep going). Continue if items are independent — one bad message shouldn't stop the rest.
The pattern: catch, log, count, continue. Then after the loop, decide if the failure rate is too high to call the run a success.
What about logging which items failed?
Print per-item, like the example. For more advanced cases, append failures to a list and print them all at the end. We'll do tagged structured logging tomorrow — same shape, more parseable output.
results = {"ok": 0, "fail": 0}
for item in items:
try:
do_work(item)
results["ok"] += 1
except Exception as e:
results["fail"] += 1
print(f"item {item} failed: {type(e).__name__}: {e}")
print(f"summary: {results}")Four-line skeleton. The try wraps only the per-item work, not the loop framing.
When each iteration's failure is independent — bad message data, individual item missing — continue. The loop's job is to do whatever it can.
When one failure indicates a systemic problem — auth expired, rate limit hit, service down — crash. The remaining iterations will all fail the same way; logging them adds noise.
A hybrid: count failures, abort if the rate exceeds a threshold:
results = {"ok": 0, "fail": 0}
for i, item in enumerate(items):
try:
do_work(item)
results["ok"] += 1
except Exception:
results["fail"] += 1
if results["fail"] > 3:
raise RuntimeError("too many failures — bailing")One line, parseable, includes both numbers:
summary: {'ok': 4, 'fail': 1}Future-you greps for summary: and reads exactly one line per script run. Compare across days: ok=4 fail=1 yesterday, ok=3 fail=2 today — something is degrading.
For cases where you want to retry just the failures later:
failed_items = []
for item in items:
try:
do_work(item)
except Exception as e:
failed_items.append({"item": item, "error": str(e)})
if failed_items:
print(f"to retry next run: {[f['item'] for f in failed_items]}")Now tomorrow's run can read failed_items and try just those, instead of the full input.
# wrong
for item in items:
try:
do_work(item)
except Exception:
pass # silent — you'll never know anything failedA caught error must be logged and counted. Silent swallowing is the worst outcome — the script reports success, but did nothing.
Create a free account to get started. Paid plans unlock all tracks.