Exceptions

raise, try/except, custom exceptions, and what not to swallow.

When an input is illegal, raise. When you can recover, except. Do not catch everything and ignore it.

Goal

Raise ValueError for bad units, catch it at the edge, and define a small custom exception.

Raise

def line_total(units, price):
    if units < 0 or price < 0:
        raise ValueError("units and price must be >= 0")
    return round(units * price * 1.16, 2)

print(line_total(2, 10))

Catch at the edge

def line_total(units, price):
    if units < 0 or price < 0:
        raise ValueError("units and price must be >= 0")
    return round(units * price * 1.16, 2)

def main():
    try:
        print(line_total(-1, 10))
    except ValueError as err:
        print("could not price line:", err)

if __name__ == "__main__":
    main()

Custom exception

class KioskError(Exception):
    """A kiosk inventory problem."""

def require_city(city):
    label = str(city or "").strip()
    if not label:
        raise KioskError("city is required")
    return label.title()

print(require_city("nakuru"))
try:
    require_city("  ")
except KioskError as err:
    print("caught", type(err).__name__, err)
Pitfall

except Exception: pass hides bugs. Catch the type you expect, or let it fail with a traceback.