Python Essentials for AI Engineer

Course Content

Python Essentials for AI Engineer

6 sections · 48 lessons

What is a try-except block?


What you need to know

How matching works

Python
def to_amount(cell):    try:        return float(cell)    except (TypeError, ValueError) as e:        # a tuple catches several types        print(f"bad cell {cell!r}: {type(e).__name__}")        return Noneprint(to_amount("499.5"))    # 499.5print(to_amount("N/A"))      # bad cell 'N/A': ValueError                             # Noneprint(to_amount(None))       # bad cell None: TypeError                             # None

Clauses are checked top to bottom, and the first match wins. So put specific exceptions before general ones; except Exception first would hide every clause after it. as e gives you the exception object, with its message and type.

Why not catch everything

  • A bare except: catches BaseException, including KeyboardInterrupt and SystemExit, so Ctrl+C no longer stops your script.
  • except Exception: is narrower but still catches your own bugs: a typo that raises NameError or an AttributeError looks the same as the failure you meant to handle.
  • except Exception: pass is the worst form: the bug still happens, but now nobody knows.

If you must catch broadly — at the top of a worker loop, for example — log the full traceback with logging.exception("...") and decide whether to re-raise.

Keep the try small

Wrap only the line that can raise the error you expect. If ten lines sit in the try, a KeyError from line 7 gets handled as if it came from line 2.

Python 3.11+: several errors at once

Concurrent code (for example, asyncio.TaskGroup running many LLM calls) can fail with several exceptions at once, bundled in an ExceptionGroup. Python 3.11 added except* to handle each type inside the group.

A real-life example

A service calls an LLM API. Different failures need different reactions: a timeout or rate limit deserves a retry with backoff; a bad request will fail again, so retrying wastes money.

Python
import timeclass RateLimitError(Exception): pass          # real SDKs define errors like theseclass BadRequestError(Exception): passattempts = {"n": 0}def call_llm(prompt):    attempts["n"] += 1    if attempts["n"] == 1: raise TimeoutError("read timed out")    if attempts["n"] == 2: raise RateLimitError("429 Too Many Requests")    return f"summary of {prompt!r}"def ask(prompt, retries=3):    for i in range(retries):        try:            return call_llm(prompt)        except (TimeoutError, RateLimitError) as e:     # transient: retry            print(f"attempt {i + 1} failed: {e}")            time.sleep(0.1 * 2 ** i)        except BadRequestError:                          # permanent: don't retry            raise    raise RuntimeError(f"gave up after {retries} attempts")print(ask("ticket 1142"))# attempt 1 failed: read timed out# attempt 2 failed: 429 Too Many Requests# summary of 'ticket 1142'

Each except names exactly what it handles. A typo inside call_llm would raise NameError, which matches neither clause and surfaces immediately instead of being retried three times.

Follow-up questions to expect

  • "Can one except catch several exception types?" — Yes, with a tuple: except (TimeoutError, ConnectionError) as e:.
  • "What is wrong with a bare except:?" — It catches everything, including KeyboardInterrupt and SystemExit, and hides real bugs. Catch specific types.
  • "What is except*?" — Syntax from Python 3.11 for handling an ExceptionGroup, where several exceptions are raised together by concurrent tasks.