Your own kinds of error
`class OutOfStock(Exception): pass` makes a new error type. Callers can catch exactly that problem, and nothing else.
- Define an error type with
class Name(Exception): passand raise it with a message - Catch your own error by name while other errors still escape
- Use different error types to choose different responses in a caller
ValueError is shared by every library you use. If your stock function raises ValueError when an item is sold out, a caller can't tell "sold out" from "someone typed twelve". A custom error type gives your problem its own name.
The pattern is two lines: class OutOfStock(Exception): and an indented pass. class makes a new type; (Exception) says it is a kind of error, so it can be raised and caught; pass means it needs nothing else. Classes can do much more, and the Classes & Objects module teaches them properly. For errors, this pattern is all you need.
Run each example to see what it prints. Running examples doesn't count toward progress.
class OutOfStock(Exception): pass stock = {"P0164": 3, "P0594": 0} def reserve(sku, qty): if stock[sku] < qty: raise OutOfStock(f"{sku}: wanted {qty}, only {stock[sku]} left") stock[sku] -= qty for sku, qty in [("P0164", 2), ("P0594", 1), ("P0164", 5)]: try: reserve(sku, qty) print("reserved", qty, sku) except OutOfStock as err: print("backorder:", err) print(stock)
class OutOfStock(Exception): pass stock = {"P0164": 3} def reserve(sku, qty): if stock[sku] < qty: raise OutOfStock(f"{sku}: only {stock[sku]} left") stock[sku] -= qty try: reserve("P9999", 1) except OutOfStock as err: print("backorder:", err)
Different error types let one caller respond differently. A feed importer can skip a row that is bad, but must stop if the file itself is the wrong one. Two error types make that decision readable.
class BadRow(Exception): pass class WrongFeed(Exception): pass def read_row(row): if "sku" not in row: raise WrongFeed("no sku column: is this the price feed?") if row["price"] < 0: raise BadRow(f"{row['sku']}: negative price {row['price']}") return (row["sku"], row["price"]) def import_feed(rows): prices = {} skipped = [] for row in rows: try: sku, price = read_row(row) except BadRow as err: skipped.append(str(err)) else: prices[sku] = price return prices, skipped print(import_feed([{"sku": "SPO-11681", "price": 7.49}, {"sku": "HOM-22847", "price": -31.0}])) try: import_feed([{"product": "x", "price": 1.0}]) except WrongFeed as err: print("import stopped:", err)
When to use it: when callers need to tell your failure apart from Python's own, or from each other. Mistakes to avoid: forgetting (Exception) (then it can't be raised: TypeError: exceptions must derive from BaseException); raising the class with no message; and defining many error types nobody catches separately: one per decision a caller makes is enough.
class Name(Exception): passmakes an error type; raise it with a message like any other.except Name:catches exactly your problem; everything else still escapes.- Separate types let callers choose: skip a bad row, stop on a wrong file.
- Next module: files. Opening, reading and writing them is where these errors meet real data.