JesusFreke
JesusFreke

Reputation: 20262

Defining a recursive type hint in Python?

Let's say I have a function that accepts a Garthok, an Iterable[Garthok], an Iterable[Iterable[Garthok]], etc.

def narfle_the_garthoks(arg):
  if isinstance(arg, Iterable):
    for value in arg:
       narfle_the_garthoks(arg)
  else:
    arg.narfle()

Is there any way to specify a type hint for arg that indicates that it accepts any level of Iterables of Garthoks? I suspect not, but thought I'd check if I'm missing something.

As a workaround, I'm just specifying a few levels deep, and then ending with Iterable[Any].

Union[Garthok,
    Iterable[Union[Garthok,
        Iterable[Union[Garthok, 
            Iterable[Union[Garthok, Iterable[Any]]]]]]]]

Upvotes: 74

Views: 35568

Answers (2)

gilch
gilch

Reputation: 11651

You can specify recursive types in the typing language by using type aliases and forward reference strings,

Garthoks = Union[Garthok, Iterable['Garthoks']]

Mypy supports recursive types by default since v0.990, and Pyright/Pylance since v2020.9.4.

Some types of forward references are handled by PEP 563. You can use them starting from Python 3.7 by doing from __future__ import annotations – Konstantin


As of Python 3.12, __future__.annotations/stringifying is not necessary if the type is defined using a type statement:

type Garthoks = Garthok | Iterable[Garthoks]]

Upvotes: 100

Adnan MARSO
Adnan MARSO

Reputation: 83

MyPy comes with this limitation, it does not support cyclic reference yet, but I found a way to work it round using TypeVars like so:

from typing import TypeVar, TypeAlias, Iterable, Union

T = TypeVar('T')
_Garthoks: TypeAlias = Union[T, Iterable[T]]
Garthoks: TypeAlias = _Garthoks[_Garthoks[_Garthoks[_Garthoks]]]
# you can nest it as deep as you need...

Currently, I find it to be the best solution util MyPy support this feature.

I hope it solves your problem.

Upvotes: -1

Related Questions