104

I have a function that looks like this:

def check_for_errors(result):
    if 'success' in result:
        return True

    if 'error' in result:
        raise TypeError

    return False

In successful run of this function, I should get a bool, but if there is an error I should get a TypeError- which is OK because I deal with it in another function.

My function first line looks like this:

def check_for_errors(result: str) -> bool:

My question is: Should I mention the error in my type hinting?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Yuval Pruss
  • 8,716
  • 15
  • 42
  • 67

4 Answers4

131

Type hinting can't say anything about exceptions. They are entirely out of scope for the feature. You can still document the exception in the docstring however.

From PEP 484 -- Type Hints:

Exceptions

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.

Guido van Rossum has strongly opposed adding exceptions to the type hinting spec, as he doesn't want to end up in a situation where exceptions need to be checked (handled in calling code) or declared explicitly at each level.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 2
    Or the use case of checking that you're not implicitly raising weird exceptions people don't expect – Erik Aronesty Dec 10 '19 at 20:00
  • 3
    @ErikAronesty: Define *weird*. All code can raise [`MemoryError`](https://docs.python.org/3/library/exceptions.html#MemoryError), and most code can raise [`KeyboardInterrupt`](https://docs.python.org/3/library/exceptions.html#KeyboardInterrupt). A good linter can help you better understand exceptions than type hinting can. – Martijn Pieters Dec 12 '19 at 18:23
  • 1
    And what if an exception is returned, not raised? E.g. NotImplemented can be returned. – Eduard Grigoryev Jan 20 '20 at 16:24
  • 6
    @EduardGrigoryev: `NotImplemented` is *not an exception*, it is a [singleton object](https://docs.python.org/3/library/constants.html#NotImplemented), a special flag value. You are thinking of [`NotImplementedError`](https://docs.python.org/3/library/exceptions.html#NotImplementedError). Exceptions can still be returned, exceptions are just classes that are subclassing `BaseException`, so you can use `Type[BaseException]` to type hint a function that accepts or returns exception classes, for example. – Martijn Pieters Jan 20 '20 at 16:27
  • Is there something like typescript never to declare you always raise a error? https://www.typescriptlang.org/docs/handbook/basic-types.html#never – shaunc Aug 01 '20 at 14:23
  • 5
    @shaunc: yes: [`typing.NoReturn`](https://docs.python.org/3/library/typing.html#typing.NoReturn). – Martijn Pieters Aug 01 '20 at 14:24
  • 1
    @Tigran'sTips I’m not here to discuss language ideas; take those to the Python ideas discussion board. – Martijn Pieters Aug 25 '22 at 18:45
11

It is usually a good idea to document the error. This means that another developer using your function will be able to handle your errors without having to read through your code.

Jim Wright
  • 5,905
  • 1
  • 15
  • 34
11

There are good reasons for making the exception path part of the type annotations of your function, at least in certain scenarios. It just provides you more help from the type checker whenever you need to understand which exceptions the caller has to handle. (If you are interested in a more in-depth analysis, I wrote a blog post about this.)

As it is out of scope of the Python typing system to indicate which exceptions a function raises (like, for instance, in Java), we need a workaround to get this. Instead of raising, we can return the exception. That way, the exception becomes part of the function signature, and the caller has to handle it, leveraging the power of the type checker.

The following code is inspired by the way exception handling is done in Rust: It provides a Result type which can either be Ok or Err. Both Ok and Err classes have an unwrap() function, which either returns the wrapped value or raises the wrapped exception.

from typing import Generic, TypeVar, NoReturn


OkType = TypeVar("OkType")
ErrType = TypeVar("ErrType", bound=Exception)


class Ok(Generic[OkType]):
    def __init__(self, value: OkType) -> None:
        self._value = value

    def unwrap(self) -> OkType:
        return self._value


class Err(Generic[ErrType]):
    def __init__(self, exception: ErrType) -> None:
        self._exception = exception

    def unwrap(self) -> NoReturn:
        raise self._exception


Result = Ok[OkType] | Err[ErrType]

Result is a Generic, and it takes two types: the type of the Ok value, and the type of the Err exception. Here it is applied to your example:

def check_for_errors(result: list[str]) -> Result[bool, TypeError]:
    if 'success' in result:
        return Ok(True)

    if 'error' in result:
        return Err(TypeError())

    return Ok(False)


def careful_method(result: list[str]):
    r = check_for_errors(result)  
    # Now, typechecker knows that r is `Result[bool, TypeError]`
    if isinstance(r, Err):
         # implement the error handling
    else:
         # implement the happy path

# If you do not want to handle the exception at this stage 
def careless_method(result: list[str]):
    check_for_errors(result).unwrap()

This is just a rough code sketch to demonstrate the principle. There is actually a more sophisticated library, poltergeist which I would recommend to use, if you consider following this approach.

Jonathan Scholbach
  • 4,925
  • 3
  • 23
  • 44
-3

In, Python you can raise a TypeError inside a method using the raise keyword

 def my_function(v1, v2):
    if not isinstance(v1, str):
       print('v1 must be str')  OR raise TypeError('type message here')
    if not isinstance(v2, int):
       print('v2 must be int')

my_function('1',1)

you can raise TypeError.

Tanveer Ahmad
  • 706
  • 4
  • 12