84

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]]]]]]]]
2
  • Does this answer your question? Recursive type annotations Commented Feb 3, 2020 at 9:04
  • 1
    For anyone looking for recursive generic types, see this issue (spoiler: still open as of now). Commented Jun 24 at 15:51

2 Answers 2

115

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]]
Sign up to request clarification or add additional context in comments.

9 Comments

Ah, gotcha. Thanks! It doesn't seem to be supported by IDEA/PyCharm either.
@JesusFreke There are a few other Python type checkers that I've heard of. Besides mypy and pycharm, there's also Facebook's Pyre and Google's pytype
@DanielH classes are types. You can also use TypeVars and subclass Generic for that [] syntax.
Some types of forward references are handled by PEP0563. You can use them starting from Python 3.7 by doing from __future__ import annotations
Unfortunately, it isn't possible to write NestedList = list[str | "NestedList"] instead of NestedList = list[Union[str, "NestedList"]] since it raises TypeError: unsupported operand type(s) for |: 'type' and 'str'.
|
-2

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.