Quick Answer

Catch specific exceptions, never a bare except. try holds the risky code, except handles a named failure, else runs only when nothing went wrong, and finally always runs. If you cannot do something useful in the handler, do not catch it.

The basic shape

def divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return "cannot divide by zero"

print(divide(10, 2))    # 5.0
print(divide(10, 0))    # cannot divide by zero

Note that 10 / 2 gives 5.0, not 5 — division always returns a float in Python 3. Use // for integer division.

The important part is except ZeroDivisionError rather than except. You are stating exactly which failure you anticipated and know how to handle. Everything else remains an error, which is what you want.

Why a bare except is a bug factory

# Do not do this
try:
    result = compute()
except:
    pass

This catches everything — including typos in your own code. If compute is misspelled, you get a NameError, which this swallows silently. The program continues with result undefined and fails somewhere unrelated, minutes later, with a message that points at the wrong place.

Bare except also catches KeyboardInterrupt, so Ctrl+C stops working, which is genuinely infuriating in a long-running script.

The rule: catch what you can handle, and let everything else crash. A crash with a traceback tells you exactly what went wrong and where. Silent failure tells you nothing, and it is much more expensive to debug.

The exceptions you will meet most

Each carries useful information in its message, which is worth reading rather than skimming:

try:
    int("abc")
except ValueError as e:
    print(e)   # invalid literal for int() with base 10: 'abc'

try:
    {"a": 1}["b"]
except KeyError as e:
    print(e)   # 'b'

KeyError prints the key that was missing — often the entire debugging session in one line. Others you will meet constantly: TypeError (wrong type, such as adding a string to a number), IndexError (list index out of range), FileNotFoundError, and AttributeError (calling a method the object does not have, usually because it is None).

else and finally, which most people skip

try:
    x = 5
except Exception:
    print("failed")
else:
    print("else ran, no exception")
finally:
    print("finally always runs")

else runs only if no exception occurred. It is useful because it keeps the try block down to the single risky line — if you put the follow-up code inside try as well, an exception raised by that code gets caught by a handler written for something else entirely.

finally runs no matter what, including when the function returns or the exception propagates. It is for cleanup: closing files, releasing locks, closing connections.

In practice, with open(...) replaces most manual finally blocks for files, because the context manager guarantees the file closes even if an error occurs.

Raising your own

Exceptions are not only for the language to throw at you. Raising one is how a function refuses invalid input:

def set_age(age):
    if age < 0:
        raise ValueError(f"age cannot be negative: {age}")
    return age

This is better than returning None or -1 to signal failure, because a returned error code can be ignored by the caller and an exception cannot. Include the offending value in the message — "age cannot be negative: -5" is far more useful than "invalid input".

When you catch an exception only to log it, re-raise it afterwards with a bare raise so the original traceback is preserved. Catching, logging and continuing is how a bug becomes invisible.

Frequently Asked Questions

What is the difference between an error and an exception in Python? In everyday use they are used interchangeably. Technically, syntax errors are detected before the program runs and cannot be caught, while exceptions occur during execution and can be handled with try/except.
Should I catch Exception instead of a bare except? It is better, because it excludes system-exiting exceptions like KeyboardInterrupt, but it is still very broad. Prefer naming the specific exceptions you expect. Catch Exception only at the top level of a program where you genuinely want to log anything before exiting.
When should I use finally? For cleanup that must happen whether or not an error occurred, such as releasing a lock or closing a connection. For files, prefer with open(), which handles the same thing more concisely.
Is it slow to use try/except? Setting up a try block is essentially free in Python; only actually raising an exception costs anything. The idiomatic style of attempting an operation and handling failure is normal and encouraged.
How do I see the full error details? Use the traceback module, or simply let the exception propagate so Python prints the traceback. The traceback shows the exact line and the chain of calls that led there, which is usually the fastest route to the cause.