Skip to content

Exceptions

Exception behavior is part of the contract: divide(1, 0) raises ZeroDivisionError and at([10, 20, 30], 9) raises IndexError at the same point, with the same type, whichever path runs them.

Run it

python  errors.ppy
ppy     errors.ppy
ppy run errors.ppy

What it prints

python errors.ppy, ppy errors.ppy, ppy run errors.ppy

3 0
3 -4
caught ZeroDivisionError
20
caught IndexError

A guard where Python would raise

@ppy.opt(3)
def at(values: list[int], index: int) -> int:
    return values[index]

Every place the program must be handed back to CPython is a core.guard in the IR: a zero divisor, an index past the end, a shift past the word. Native code takes the guard's failure edge, and the wrapper re-runs the Python body, which raises what Python raises.

safe_divide checks b == 0 itself and never fails a guard. divide does not, and its ZeroDivisionError comes from the fallback.

The optimizer keeps the order

Optimization passes do not move or drop an operation that can raise unless they can prove it cannot. Raising is an effect (may_raise) the passes read like any other, so an IndexError on the third element still happens after the print of the second.

Where the code comes from

errors.ppy is hand-written; there is no .py source and no conversion step.

Read on: Effects and purity ยท Numerics

18_errors/errors.ppy

import ppy


@ppy.pure
@ppy.opt(3)
def safe_divide(a: int, b: int) -> int:
    if b == 0:
        return 0
    return a // b


@ppy.opt(3)
def divide(a: int, b: int) -> int:
    return a // b


@ppy.opt(3)
def at(values: list[int], index: int) -> int:
    return values[index]


def main() -> None:
    print(safe_divide(7, 2), safe_divide(7, 0))
    print(divide(7, 2), divide(-7, 2))
    try:
        divide(1, 0)
    except ZeroDivisionError as error:
        print("caught", type(error).__name__)
    print(at([10, 20, 30], 1))
    try:
        at([10, 20, 30], 9)
    except IndexError as error:
        print("caught", type(error).__name__)


main()

Source: examples/18_errors.