Skip to content

Exceptions & context managers

This page covers errors your program can raise and catch (raise, try, except, else, finally), the built-in exception types you will meet, what happens to cleanup code when a function returns early, and the with statement. Exceptions in d/Python behave as they do in Python: you raise one, you catch it by its class, and finally blocks run on the way out. They are different from the messages d/OS itself gives when it will not run a program at all; When the Compiler Says No explains that difference, and this page shows how exceptions work.

try, except, else, finally

def parse(text):
    if not text:
        raise ValueError("empty")
    return int(text)

for sample in ["42", "", "x1"]:
    try:
        print("parsed", parse(sample))
    except ValueError as error:
        print("ValueError:", error)
    else:
        print("no error")
    finally:
        print("done with", repr(sample))

The builtin exceptions you will meet

try:
    [1, 2, 3][5]
except IndexError as error:
    print("IndexError:", error)
try:
    {"a": 1}["b"]
except KeyError as error:
    print("KeyError:", error)
try:
    print(7 % 0)
except ZeroDivisionError as error:
    print("ZeroDivisionError:", error)
try:
    next(iter([]))
except StopIteration:
    print("StopIteration")
try:
    chr("x")
except TypeError as error:
    print("TypeError:", error)

ValueError, TypeError, IndexError, KeyError, ZeroDivisionError, StopIteration, EOFError, AssertionError and RuntimeError are raised in the same situations as in Python, with CPython's messages. KeyError shows its key the way repr would, quotes included. When you raise one yourself, give it either no argument or one argument, and that argument must be a value that cannot change: a number, a string, or a tuple made only of such values.

Re-raising and unwinding

def bottom():
    try:
        raise ValueError("bottom")
    finally:
        print("unwinding bottom")

def middle():
    try:
        bottom()
    finally:
        print("unwinding middle")

try:
    middle()
except ValueError as error:
    print("caught", error)

try:
    try:
        raise ValueError("inner")
    except ValueError:
        print("seen, re-raising")
        raise
except ValueError as error:
    print("outer:", error)

finally blocks run when an exception passes through, when a function returns, when a loop is left with break or continue, and all the way up through nested and recursive calls, in the order Python defines. A bare raise inside except re-raises the exception being handled.

with

class Scope:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        print("enter", self.label)
        return self.label

    def __exit__(self, kind, value, traceback):
        print("exit", self.label, "with", kind is None)
        return kind is ValueError

with Scope("a") as first, Scope("b") as second:
    print("inside", first, second)
    raise ValueError("suppressed by b")
print("continued")

def early():
    with Scope("c"):
        return "returned"
    return "fallback"

print(early())

A with statement calls __enter__ on each manager from left to right and __exit__ from right to left, and __exit__ is only called for a manager whose __enter__ finished. When the body ends normally, or leaves through return, break or continue, __exit__ receives three None arguments. When an exception is on its way out, __exit__ receives the exception's builtin class and the exception object itself; if __exit__ returns a true value the exception is swallowed, as Scope("b") does above, and otherwise it keeps going.

The limits are spelled out. __exit__ must take exactly four ordinary positional parameters, and your code must not read the fourth one, the traceback. Not yet supported: context managers whose __enter__ and __exit__ are not plain methods defined on the class, async with, the contextlib module, and file objects from open(). Two compiler rules follow from suppression: a variable assigned only inside a with body whose exception was swallowed must be assigned again before it is used afterwards, because the compiler cannot know whether the body finished, and a function whose only return sits inside a with needs another return of the same type after the block, as early shows.

What is not there

Defining your own exception classes, traceback objects, chaining exceptions with raise ... from ..., and inspecting exceptions through reflection are not yet supported; the compiler stops and says so. Separately, there are the cases where d/OS itself will not run or continue your program: a missing part of the host (the part that supplies files, sound or the network), a request for memory that cannot be met, or a program that fails the checks it must pass before it runs. Those are not Python exceptions and except does not catch them. They report a class, a rule number and a detail so you can tell exactly why the program stopped.

What the tests cover

Every claim on this page comes from a test program that runs in both d/Python and CPython and must print the same thing. The corpus page lists those programs, with their reviewed output.