Python Type Hints - What’s the point of NoReturn?

2021-05-20 Hey, who did a no return of half my letters!?

Sometimes functions never return, for example by always raising an exception. For such functions’ return types, we can “get away” with using None, but it’s best to use the special NoReturn type). This allows Mypy to better find unreachable code (as covered previously). It also shows future readers that the lack of return is intentional.

For example, take this code:

def always_raise() -> None:
    raise RuntimeError("Uh oh")


def main() -> None:
    always_raise()
    print("Ok!")

Calling always_raise() means that we’ll have a RuntimeError, and print("Ok!") cannot be reached. But if we check with Mypy, it cannot detect this:

$ mypy --warn-unreachable example.py
Success: no issues found in 1 source file

Let’s update the code to instead use NoReturn:

from typing import NoReturn


def always_raise() -> NoReturn:
    raise RuntimeError("Uh oh")


def main() -> None:
    always_raise()
    print("Ok!")

Now Mypy tells us the print() will never run:

$ mypy --warn-unreachable example.py
example.py:10: error: Statement is unreachable
Found 1 error in 1 file (checked 1 source file)

Great!

It’s best to use NoReturn for any function that never returns. These includes functions with infinite loops or use of OS API’s that end the process:

import os
from typing import NoReturn


def loop_forever() -> NoReturn:
    while True:
        ...  # loop never exits with 'break'


def abruptly_die() -> NoReturn:
    os._exit(123)

Fin

We are now past the point of NoReturn,

—Adam


Want better tests? Check out my book Speed Up Your Django Tests which teaches you to write faster, more accurate tests.


Subscribe via RSS, Twitter, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: mypy, python