Type hint that a function never returns

Question:

Python’s new type hinting feature allows us to type hint that a function returns None

def some_func() -> None:
    pass

… or to leave the return type unspecified, which the PEP dictates should cause static analysers to assume that any return type is possible:

Any function without annotations should be treated as having the most general type possible

However, how should I type hint that a function will never return? For instance, what is the correct way to type hint the return value of these two functions?

def loop_forever():
    while True:
        print('This function never returns because it loops forever')

def always_explode():
    raise Exception('This function never returns because it always raises')

Neither specifying -> None nor leaving the return type unspecified seems correct in these cases.

Asked By: Mark Amery

||

Answers:

In july 2016, there was no answer to this question yet (now there is NoReturn; see the new accepted answer). These were some of the reasons:

  • When a function doesn’t return, there is no return value (not even None) that a type could be assigned to. So you are not actually trying to annotate a type; you are trying to annotate the absence of a type.

  • The type hinting PEP has only just been adopted in the standard, as of Python version 3.5. In addition, the PEP only advises on what type annotations should look like, while being intentionally vague on how to use them. So there is no standard telling us how to do anything in particular, beyond the examples.

  • The PEP has a section Acceptable type hints stating the following:

    Annotations must be valid expressions that evaluate without raising exceptions at the time the function is defined (but see below for forward references).

    Annotations should be kept simple or static analysis tools may not be able to interpret the values. For example, dynamically computed types are unlikely to be understood. (This is an intentionally somewhat vague requirement, specific inclusions and exclusions may be added to future versions of this PEP as warranted by the discussion.)

    So it tries to discourage you from doing overly creative things, like throwing an exception inside a return type hint in order to signal that a function never returns.

  • Regarding exceptions, the PEP states the following:

    No syntax for listing explicitly raised exceptions is proposed. Currently the only known use case for this feature is documentational, in which case the recommendation is to put this information in a docstring.

  • There is a recommendation on type comments, in which you have more freedom, but even that section doesn’t discuss how to document the absence of a type.

There is one thing you could try in a slightly different situation, when you want to hint that a parameter or a return value of some “normal” function should be a callable that never returns. The syntax is Callable[[ArgTypes...] ReturnType], so you could just omit the return type, as in Callable[[ArgTypes...]]. However, this doesn’t conform to the recommended syntax, so strictly speaking it isn’t an acceptable type hint. Type checkers will likely choke on it.

Conclusion: you are ahead of your time. This may be disappointing, but there is an advantage for you, too: you can still influence how non-returning functions should be annotated. Maybe this will be an excuse for you to get involved in the standardisation process. 🙂

I have two suggestions.

  1. Allow omitting the return type in a Callable hint and allow the type of anything to be forward hinted. This would result in the following syntax:

    always_explode: Callable[[]]
    def always_explode():
        raise Exception('This function never returns because it always raises')
    
  2. Introduce a bottom type like in Haskell:

    def always_explode() -> ⊥:
        raise Exception('This function never returns because it always raises')
    

These two suggestions could be combined.

Answered By: Julian

Even though “PEP 484 — Type Hints” standard mentioned both in question and in the answer yet nobody quotes its section: The NoReturn type that covers your question.

Quote:

The typing module provides a special type NoReturn to annotate functions that never return normally. For example, a function that unconditionally raises an exception:

from typing import NoReturn

def stop() -> NoReturn:
    raise RuntimeError('no way')

The section also provides examples of the wrong usages. Though it doesn’t cover functions with an endless loop, in type theory they both equally satisfy never returns meaning expressed by that special type.

Answered By: kuza

From Python 3.11, the new bottom type typing.Never should be used to type functions that don’t return, as

from typing import Never

def always_explode() -> Never:
    raise

This replaces typing.NoReturn

… to make the intended meaning more explicit.

I’m guessing at some point they’ll deprecate NoReturn in that context, since both are valid in 3.11.

Answered By: joel
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.