PART 2 · PYTHON FOUNDATIONS · CHAPTER 7
Errors, exceptions and context managers
FreeLesson 30 of 78
A pipeline that crashes on one bad row loses the other million. Catch narrowly, count what you skip, and always clean up.
def parse_amount(text):
try:
value = float(text)
except ValueError:
print(" except: bad value", repr(text))
return None
else:
print(" else: parsed", value)
return value
finally:
print(" finally: always runs")
for t in ["1198.00", "abc"]:
print(t, "->", parse_amount(t))Output
else: parsed 1198.0 finally: always runs 1198.00 -> 1198.0 except: bad value 'abc' finally: always runs abc -> None
class DataQualityError(Exception):
"""raised when a batch fails a hard check"""
def check_batch(rows):
bad = [r for r in rows if r["amount"] <= 0]
if bad:
raise DataQualityError(f"{len(bad)} rows <= 0")
try:
check_batch([{"amount": 10}, {"amount": -50}])
except DataQualityError as e:
print("blocked load:", e)
try:
int("12a")
except ValueError as e:
raise RuntimeError("could not parse order_id") from eOutput
blocked load: 1 rows <= 0 RuntimeError: could not parse order_id
class Connection:
def __enter__(self):
print("open connection")
return self
def __exit__(self, exc_type, exc, tb):
name = exc_type.__name__ if exc_type else None
print("close connection, error:", name)
return False # do not swallow errors
try:
with Connection() as conn:
print("loading rows")
raise TimeoutError("warehouse timeout")
except TimeoutError:
print("caught after cleanup")Output
open connection loading rows close connection, error: TimeoutError caught after cleanup
with calls __exit__ whether the block succeeds or fails. That is why with open(...) never leaks file handles. Page 38 shows the shorter @contextmanager version.
Practice before you move on
- Convert the amount column of orders_dirty.csv with float, catch ValueError, and print the values that fail.
- Write a context manager that prints start and end around a block, even when the block raises.