Python handles errors with try/except/finally, and — unlike many languages — actively encourages using exceptions for expected, everyday control flow, not just rare error cases.
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Cannot divide by zero")
return None
except TypeError as e: # catch a specific type, bind it to a name
print(f"Type error: {e}")
return None
else: # runs only if NO exception occurred
print("Division succeeded")
return result
finally: # ALWAYS runs, exception or not
print("Division attempt finished")
divide(10, 2)
divide(10, 0)
Multiple except clauses can catch different exception types separately, checked top-to-bottom, with the first matching clause handling the exception; catching the general Exception should be a last resort, since it can silently swallow bugs you didn't anticipate.
The else clause on a try block (distinct from a loop's else, covered earlier) runs only when the try block completes without raising — useful for code that should only execute after a successful operation, kept separate from the try block itself so it isn't accidentally caught by the except clauses.
finally always runs — whether the try succeeded, raised an exception, or even returned early — making it the right place for guaranteed cleanup, like closing a resource. Python's philosophy, often summarized as "EAFP" (Easier to Ask Forgiveness than Permission), favors trying an operation and catching the exception if it fails, over checking preconditions exhaustively beforehand ("LBYL," Look Before You Leap) — this is why exceptions are used far more liberally in idiomatic Python than in many other languages.