653

I have a function in python that can either return a bool or a list. Is there a way to specify the return types using type hints?

For example, is this the correct way to do it?

def foo(id) -> list or bool:
    ...
Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Yahya Uddin
  • 26,997
  • 35
  • 140
  • 231
  • 32
    @PadraicCunningham Perhaps the implementation is *I'll send you my Id, you send me either a list or a boolean* :D – Bhargav Rao Nov 26 '15 at 19:10
  • 7
    @PadraicCunningham Polymorphism. If your function performs a check on the input, whatever it is, you want to get a boolean when you feed one variable or get a list of booleans when you feed a list of variables. – Guimoute Feb 20 '20 at 16:33
  • 2
    seems like a XY problem. I'd ask why a function would return a list or a bool in the first place. – Carlo Oct 05 '22 at 17:52
  • To return multiple types, you have to use a Tuple https://stackoverflow.com/questions/58101021/python-type-hints-for-function-returning-multiple-return-values – Nav Feb 05 '23 at 06:42

4 Answers4

941

From the documentation - Union Type:

A union object holds the value of the | (bitwise or) operation on multiple type objects. These types are intended primarily for type annotations. The union type expression enables cleaner type hinting syntax compared to typing.Union.

This use of | was added in Python 3.10. Hence the proper way to represent more than one return data type is:

def foo(client_id: str) -> list | bool:

For earlier versions, use typing.Union:

from typing import Union


def foo(client_id: str) -> Union[list, bool]:

But do note that typing is not enforced. Python continues to remain a dynamically-typed language. The annotation syntax has been developed to help during the development of the code prior to being released into production. As PEP 484 states, "no type checking happens at runtime."

>>> def foo(a: str) -> list:
...     return "Works"
... 
>>> foo(1)
'Works'

As you can see I am passing an int value and returning a str. However the __annotations__ will be set to the respective values.

>>> foo.__annotations__ 
{'return': <class 'list'>, 'a': <class 'str'>}

Please go through PEP 483 for more about Type hints. Also see What are type hints in Python 3.5??

Kindly note that this is available only for Python 3.5 and upwards. This is mentioned clearly in PEP 484.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
  • Is there an equivalent in Python 3.4 – Yahya Uddin Nov 26 '15 at 19:43
  • 3
    @YahyaUddin Nope - [PEP 484](https://www.python.org/dev/peps/pep-0484/) :'( .... It's only for Python3.5 upwards. – Bhargav Rao Nov 26 '15 at 19:45
  • But I know type hints still work for Python 3.4 as well i.e. `def foo(id: str)`. Isn't this also type hints? – Yahya Uddin Nov 26 '15 at 19:47
  • @YahyaUddin Naw. They don't work in 3.4. Type hints are Python3.5 only. Did you check you [python version](http://stackoverflow.com/questions/1093322/how-do-i-check-what-version-of-python-is-running-my-script) correctly? – Bhargav Rao Nov 26 '15 at 19:49
  • 1
    @YahyaUddin Quite surprising. Did you mean [Function Annotations](https://docs.python.org/3/tutorial/controlflow.html#function-annotations) by any chance? – Bhargav Rao Nov 26 '15 at 19:52
  • 2
    So let me see if I got this. Python 3.4 has function annotations that dosen't do anything other than annotate that is NOT enforced. But in Python 3.5 this is actual type checking. – Yahya Uddin Nov 26 '15 at 19:54
  • 1
    @YahyaUddin no. It's 2022 and Python 3.10 is still dynamic and doesn't enforce any typing. – sourcream Aug 24 '22 at 22:23
  • 2
    If you want typing to be enforced, there are several modules that do that, the one I use is typeguard (I have no association with its author). To use it, do `from typeguard import typechecked`, then add `@typechecked` on a new line before any function or class for which you want typing to be enforced. You may need to do `pip install typeguard` for this to work. – Donald Duck Jan 30 '23 at 17:59
146

In case anyone landed here in search of "how to specify types of multiple return values?", use a tuple of [type_value1, ..., type_valueN].

In Python 3.9+:

def f() -> tuple[dict, str]:
    a = {1: 2}
    b = "hello"
    return a, b

In earlier versions, use typing.Tuple:

from typing import Tuple

def f() -> Tuple[dict, str]:
    ...

More info: How to annotate types of multiple return values?

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Anton Khodak
  • 3,250
  • 2
  • 14
  • 18
  • 11
    As per the docs, this is the correct way to do it (returning N types): https://docs.python.org/3/library/typing.html#typing.Tuple; whereas returning an Union should actually return ONE of N type values. – Nico Villanueva Oct 08 '20 at 10:45
134

Python 3.10 or newer: Use |. Example for a function which takes a single argument that is either an int or str and returns either an int or str:

def func(arg: int | str) -> int | str:
    #         ^^^^^^^^^     ^^^^^^^^^ 
    #        type of arg   return type

Python 3.5 - 3.9: Use typing.Union:

from typing import Union

def func(arg: Union[int, str]) -> Union[int, str]:
    #         ^^^^^^^^^^^^^^^     ^^^^^^^^^^^^^^^ 
    #           type of arg         return type

For the special case of X | None you can use Optional[X].

xjcl
  • 12,848
  • 6
  • 67
  • 89
  • 3
    This answer, to me, is the most helpful and illuminating. It has basically all the other answers' information (except those answers or comments that suggest a non-multiple-type alternative) in a very concise form. Though the OP did not specifically ask about multiple argument types, the description of how to use them as well as multiple return types makes the answer much more complete. Though I give a single-typed example, I also appreciate that the answer removes any confusion (such as I had) as to specifically what was different (if anything) between `foo( bar: int )` and `foo( bar ) -> int` – bballdave025 Dec 06 '21 at 20:38
  • 2
    Thanks for the praise (: I tried to give an answer that was short and visual – xjcl Dec 06 '21 at 22:39
  • 1
    If I use `|`, will it be compatible with prior Python versions (since type hinting is just a "hint")? Thanks. – Floella Sep 29 '22 at 22:54
  • 3
    @Floella it will not be compatible, the syntax changed in 3.10 to include the Union operator, before that it is a syntax error. – mfurseman Nov 10 '22 at 11:47
  • 1
    @mfurseman Actually, it's the operator semantics that changed. It was a `TypeError` before, not a syntax error. – wjandrea Jan 27 '23 at 15:59
78

The statement def foo(client_id: str) -> list or bool: when evaluated is equivalent to def foo(client_id: str) -> list: and will therefore not do what you want.

The native way to describe a "either A or B" type hint is Union (thanks to Bhargav Rao):

def foo(client_id: str) -> Union[list, bool]:

Or, starting with Python 3.10 and beyond, using the | operator:

def foo(client_id: str) -> list | bool:

I do not want to be the "Why do you want to do this anyway" guy, but maybe having 2 return types isn't what you want:

If you want to return a bool to indicate some type of special error-case, consider using Exceptions instead. If you want to return a bool as some special value, maybe an empty list would be a good representation. You can also indicate that None could be returned with Optional[list]

Felk
  • 7,720
  • 2
  • 35
  • 65
  • 9
    There are uses where returning multiple types may be what you want: for instance if you need to return one of some set of subtypes, but not other subtypes, or if you are attempting to process data and want to return the raw form if processing isn't available. Also, if you are wrapping legacy code it can be quite useful, because it helps the upgrade process and/or see awkward places. – Nathaniel Ford Nov 26 '15 at 19:17
  • 1
    The exceptions and empty list idea was helpful as well. thanks – Yahya Uddin Nov 26 '15 at 19:49
  • 2
    The `|` operator doesn't work when I try to indicate I'm allowing my function to return both `str` and `None` (i.e. `-> str | None`). In this case, I get `TypeError: unsupported operand type(s) for |: 'type' and 'NoneType'`. `Union` works properly, though. – muxevola Jun 15 '21 at 21:17
  • 1
    as xjcl described in their answer, in case of "X or None" you can also use `Optional[X]` – Felk Jun 28 '21 at 19:52