So I wrote a function that sometimes doesn’t return i.e. it will raise an exception under certain conditions.
from typing import NoReturn
def foo(a: int) -> int|NoReturn:
if not is instance(a, int):
raise TypeError("a must be int")
That raises error that
| isn’t defined for
NoReturn. Strangely I could do
Union[int, NoReturn] but I’m assuming mypy doesn’t like that given the final comment on this answer
Am I doing an anti pattern here? Is it wrong to annotate that a function could raise an exception? If so wouldn’t it be useful to indicate what type of exceptions to possibly except them?
Edit: for some reason I’m remembering someone mentioning on a mailing list that annotating possible exceptions is pointless bc a function being invoked in the function in question (invoked by the invoked) could raise and it’s a rabbit hole and I guess that makes sense…maybe
I think you’re on the right track,
NoReturn is not for exceptions being raised, it’s literally for functions that cannot ever return (which are uncommon if not rare).
I think you’re on the right track,
NoReturn is not for exceptions being raised
It can be, if the function can only raise exceptions. This is in fact the example the
mypy docs give:
More types - mypy 0.971 documentation
Speculation, but I suspect the commonest case for
NoReturn is functions that wrap
In general, any function that calls Python code could raise literally any exception you have ever heard of, or even exceptions that didn’t exist before you called the function.
len(obj) is guaranteed to return an int, or raise TypeError, provided
obj is a builtin. But as soon as
obj is a custom Python class with a
__len__ method, it could raise anything, deliberately or due to bugs.
Any function, builtin or not, could raise and not return if given invalid input. So every function with a return annotation should be read as “return this type, or raise”.
Since every function could, in principle, raise and not return, it would be a waste of time to annotate everything that could raise. But that is not the purpose of the NoReturn annotation:
So the purpose of
NoReturn is to annotate functions that unconditionally always raise, or exit without either returning or raising.
(I guess it could also be used to annotate a function which never terminates.)
Union[NoReturn, T] for some type T should be considered an error by mypy, I don’t know why it isn’t.
And given that unions with
NoReturn should not be supported, there is no point in supporting the | operator.
Yeah, I’d agree with that. I’ve written a number of functions with infinite loops in them, intending to have them terminate with an exception at some point (most commonly KeyboardInterrupt or EOFError).
So Union[NoReturn, T] for some type T should be considered an error by mypy, I don’t know why it isn’t.
I’m assuming it is, given the SO post I linked. It is not an error in python unlike
NoReturn|T, for some type
T, which raises an error in python. Which seems inconsistent given
| is just shorthand for
What version of Python are you using?
In 3.10, you can form unions of NoReturn with other types:
# returns typing.Union[int, typing.NoReturn]
Possibly this has changed in 3.11. If it has, it is very likely that NoReturn’s
__or__ method raises an error, but calling Union directly does not.
Union[NoReturn, T] to give an error, it would need to hard-code Union to recognise NoReturn as special. Maybe nobody has bothered to do this?
Maybe you should ask on the typing mailing list to see whether this is deliberate or just an oversight.
| operator for types is still new. It was introduced in Python 3.10:
A new type union operator was introduced which enables the syntax
X | Y . This provides a cleaner way of expressing ‘either type X or type Y’ instead of using
typing.Union, especially in type hints.
Please next time copy the error message instead of describing it.
You can use this new operator in type hints with older versions of Python (as far as 3.7) if your type checker (mypy, pyright…) supports it. Just tell the Python interpreter to not evaluate the annotation expressions by putting this
__future__ import at the beginning of a module:
from __future__ import annotations
Thank you. Yes I should have just copied the error. I switch between 3.7, 3.9, and 3.10 (different systems) that I get features confused.