How to Catch Multiple Exceptions in Python

If you need one try block to catch multiple exceptions from messy data, list the exception types in a tuple: except (ValueError, KeyError) as err:. Python runs that block when any exception in the tuple is raised.

Where people get stuck is deciding when one shared handler is fine and when each error needs its own fix.

In this tutorial, we’ll catch multiple exceptions in Python six ways, using one small set of US orders that breaks four different ways. I ran every script on Python 3.12.5, and the new syntax on 3.14.8.

The sample orders we’ll use to catch multiple exceptions

A small pricing function turns each order into a unit price with state sales tax. Only the first of five orders is clean:

  • A-1002: qty is "two", a ValueError.
  • A-1003: qty is "0", a ZeroDivisionError.
  • A-1004: ships to Guam, which isn’t in TAX, a KeyError.
  • A-1005: qty is None, a TypeError.

For parsing text values, see converting a string to an integer and converting a string to a float in Python.

Unhandled, the loop dies on order A-1002:

TAX = {"TX": 0.0625, "CA": 0.0725, "NY": 0.04, "WA": 0.065}
orders = [
    {"id": "A-1001", "state": "TX", "qty": "3", "total": "89.97"},
    {"id": "A-1002", "state": "CA", "qty": "two", "total": "45.00"},
    {"id": "A-1003", "state": "NY", "qty": "0", "total": "19.99"},
    {"id": "A-1004", "state": "GU", "qty": "1", "total": "12.50"},
    {"id": "A-1005", "state": "WA", "qty": None, "total": "30.00"},
]

def unit_price(order):
    qty = int(order["qty"])
    price = float(order["total"]) / qty
    return round(price * (1 + TAX[order["state"]]), 2)


for order in orders:
    print(order["id"], unit_price(order))

Output:

A-1001 31.86
Traceback (most recent call last):
  File "C:\pyguides\orders.py", line 17, in <module>
    print(order["id"], unit_price(order))
                       ^^^^^^^^^^^^^^^^^
  File "C:\pyguides\orders.py", line 11, in unit_price
    qty = int(order["qty"])
          ^^^^^^^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'two'
Command Prompt traceback of the Python orders script stopping with ValueError on the quantity two
Order A-1001 prices at $31.86, then int('two') stops the whole run with a ValueError.

Method 1: Catch multiple exceptions in one except with a tuple (Recommended)

Use a tuple when every error type gets the same response: report the bad order and keep going. Same five orders, now wrapped in try:

from orders import orders, unit_price

for order in orders:
    try:
        print(f"{order['id']}  ${unit_price(order):.2f}")
    except (ValueError, ZeroDivisionError, KeyError, TypeError) as err:
        print(f"{order['id']}  skipped -> {type(err).__name__}: {err}")

Output:

A-1001  $31.86
A-1002  skipped -> ValueError: invalid literal for int() with base 10: 'two'
A-1003  skipped -> ZeroDivisionError: float division by zero
A-1004  skipped -> KeyError: 'GU'
A-1005  skipped -> TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
Command Prompt output of Python catching ValueError, ZeroDivisionError, KeyError and TypeError in one except tuple
One except tuple prices A-1001 at $31.86 and skips the other four orders, naming each exception type.

How does this code work?

Python compares the raised exception against each class in the tuple. For A-1002, ValueError matches, so the block runs with err holding the exception.

type(err).__name__ gives the class name, so one handler still says what went wrong. More in checking the type of a variable in Python.

Important: it must be a tuple (see creating a tuple in Python). A list compiles, then fails with TypeError: catching classes that do not inherit from BaseException is not allowed.

Method 2: Handle each exception differently with separate except blocks

A shared message isn’t always enough. A missing tax rate means updating a table; a zero quantity means the order is wrong.

So give each its own except block, plus else and finally:

from orders import orders, unit_price

checked = 0
for order in orders:
    try:
        price = unit_price(order)
    except ValueError:
        print(f"{order['id']}  qty isn't a number: {order['qty']!r}")
    except ZeroDivisionError:
        print(f"{order['id']}  qty is 0, can't split ${order['total']}")
    except KeyError as err:
        print(f"{order['id']}  no sales tax rate for {err}")
    except TypeError:
        print(f"{order['id']}  qty is missing")
    else:
        print(f"{order['id']}  ${price:.2f}")
    finally:
        checked += 1

print(f"Checked {checked} orders")

Output:

A-1001  $31.86
A-1002  qty isn't a number: 'two'
A-1003  qty is 0, can't split $19.99
A-1004  no sales tax rate for 'GU'
A-1005  qty is missing
Checked 5 orders
Command Prompt output of Python multiple except blocks giving a different message per error, with else and finally
Each failing order gets its own message, the clean order prints its price from else, and finally counts all five.

How does this code work?

Python checks the except clauses top to bottom and runs only the first one that matches. The rest are skipped for that order.

else runs only when the try body raised nothing. finally runs every time, error or not, so the count stays honest.

My guide to the Python dictionary KeyError covers why keys go missing.

Method 3: Catch a parent exception class like LookupError or ArithmeticError

KeyError and IndexError both inherit from LookupError; ZeroDivisionError inherits from ArithmeticError.

Catching the parent catches every child, which is handy when they need the same fix. Same orders again:

from orders import orders, unit_price

for order in orders[2:4]:
    try:
        unit_price(order)
    except LookupError as err:
        print(order["id"], "LookupError caught", type(err).__name__)
    except ArithmeticError as err:
        print(order["id"], "ArithmeticError caught", type(err).__name__)

try:
    unit_price(orders[3])
except Exception:
    print("Exception matched first, so the KeyError block below never runs")
except KeyError:
    print("never printed")

Output:

A-1003 ArithmeticError caught ZeroDivisionError
A-1004 LookupError caught KeyError
Exception matched first, so the KeyError block below never runs

How does this code work?

An except clause matches the class you name or any subclass of it. That’s why Guam’s KeyError fires the LookupError block.

The last try shows the trap: except Exception sits first and matches everything, so the KeyError block below it is dead code.

So put specific classes first and broad ones last. The same index error on its own is covered in fixing IndexError: list index out of range.

Method 4: Python 3.14 except without parentheses (PEP 758)

Python 3.14 lets you drop the parentheses when you list several exceptions and don’t use as. Here’s one script run on both versions, using the sample’s quantity values:

import sys
print("Python", sys.version.split()[0])

for qty in ["2", "two", None]:
    try:
        print(int(qty) * 10)
    except ValueError, TypeError:
        print(f"bad qty: {qty!r}")
Before and after: Python 3.12 rejects except ValueError, TypeError with a SyntaxError, Python 3.14 runs it
Python 3.12 stops with SyntaxError: multiple exception types must be parenthesized; Python 3.14 runs it and handles both bad values.

On 3.14 the output is:

Python 3.14.8
20
bad qty: 'two'
bad qty: None

Add as err, though, and 3.14 still wants them: SyntaxError: multiple exception types must be parenthesized when using 'as'.

Note: if your code has to run on 3.13 or older, keep the parentheses. A SyntaxError is unusual among the types of errors in Python: it fails before a single line runs.

Method 5: Ignore multiple exceptions with contextlib.suppress()

Sometimes a failure needs no message at all. contextlib.suppress() takes any number of exception classes and silently swallows them inside its with block:

from orders import orders, unit_price

from contextlib import suppress

prices = {}
for order in orders:
    with suppress(ValueError, ZeroDivisionError, KeyError, TypeError):
        prices[order["id"]] = unit_price(order)

print(prices)
print(f"{len(prices)} of {len(orders)} orders priced")

Output:

{'A-1001': 31.86}
1 of 5 orders priced

Only A-1001 gets a price: 1 of 5 orders.

How does this code work?

When a listed exception escapes the with block, suppress stops it there, and the assignment never happens.

It’s the same as try / except / pass, just shorter. Inside a Python for loop, the loop just moves on.

Pro Tip: only use suppress() when losing the record is truly fine. Four orders vanished here without a trace.

Method 6: Catch multiple exceptions raised together with except* and ExceptionGroup

Everything so far handles one exception at a time. To report every problem together, Python 3.11 added ExceptionGroup, plus except* to pull matches out of the group:

from orders import orders, unit_price

problems = []
for order in orders:
    try:
        unit_price(order)
    except (ValueError, ZeroDivisionError, KeyError, TypeError) as err:
        err.add_note(order["id"])
        problems.append(err)

try:
    raise ExceptionGroup("order import failed", problems)
except* (ValueError, TypeError) as eg:
    print("Bad qty:      ", [e.__notes__[0] for e in eg.exceptions])
except* ZeroDivisionError as eg:
    print("Zero qty:     ", [e.__notes__[0] for e in eg.exceptions])
except* KeyError as eg:
    print("Unknown state:", [e.__notes__[0] for e in eg.exceptions])

Output:

Bad qty:       ['A-1002', 'A-1005']
Zero qty:      ['A-1003']
Unknown state: ['A-1004']
Command Prompt output of Python except star splitting an ExceptionGroup of order errors by exception type
One ExceptionGroup holds all four failures, and each except* clause pulls out its own order IDs.

How does this code work?

add_note() (also 3.11+) tags each exception with its order ID, and the group carries all four.

Unlike plain except, several except* clauses can run for one group, each getting just its matches.

Any type no clause matches is re-raised. To log full tracebacks instead, see printing a stack trace in Python.

Which method should you use to catch multiple exceptions?

SituationUseWhy
Same response for every errorexcept (A, B) as err:One block; err names the type
Different fix per errorSeparate except blocksOwn message or recovery
All children need one fixParent class (LookupError)Covers every subclass
Python 3.14+ only, no asexcept A, B:Less punctuation, same behavior
Failure is truly harmlesscontextlib.suppress(A, B)Shortest, but leaves no record
Several errors at onceExceptionGroup + except*Reports them all

I reach for the tuple first and split it into separate blocks once the fixes differ.

Things to keep in mind when catching multiple exceptions

  • Use a tuple, never a list. A list raises TypeError as soon as an exception arrives.
  • Order specific before broad. except Exception placed first hides every block below it.
  • Avoid a bare except:. It also catches KeyboardInterrupt and the SystemExit from Python exit commands.
  • Keep the try body small. A wide block can hide bugs, like a ValueError: math domain error from an unrelated line.

A few more error guides you’ll probably want:

Frequently asked questions

How do I catch multiple exceptions in one line in Python?

Use a tuple: except (ValueError, KeyError) as err:. Python 3.14+ also accepts except ValueError, KeyError: without as.

Can I use a list instead of a tuple in an except clause?

No. A list compiles, but when an exception is raised Python fails with TypeError: catching classes that do not inherit from BaseException is not allowed.

How do I know which exception was caught?

Use as err, then type(err).__name__ or isinstance(err, KeyError).

Does the order of except blocks matter?

Yes. Python runs the first matching block only, so a parent class like Exception placed first hides the more specific blocks after it.

What’s the difference between except and except*?

except handles one exception. except* (Python 3.11+) handles an ExceptionGroup, and several except* clauses can run for the same group.

Where is exception handling documented?

See Handling Exceptions in the official Python tutorial.