59

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]]]]]]]]
Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
JesusFreke
  • 19,784
  • 5
  • 65
  • 68
  • Does this answer your question? [Recursive type annotations](https://stackoverflow.com/questions/53638973/recursive-type-annotations) – Georgy Feb 03 '20 at 09:04

2 Answers2

78

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

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

Note that recursive types are not yet supported by mypy. But it will likely be added eventually.


Update 2020/9/14: Microsoft announces support for recursive types in Pyright/Pylance.


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

Azat Ibrakov
  • 9,998
  • 9
  • 38
  • 50
gilch
  • 10,813
  • 1
  • 23
  • 28
  • Ah, gotcha. Thanks! It doesn't seem to be supported by IDEA/PyCharm either. – JesusFreke Dec 19 '18 at 05:31
  • @JesusFreke There are a few other Python type checkers that I've heard of. Besides mypy and pycharm, there's also [Facebook's Pyre](https://github.com/facebook/pyre-check) and [Google's pytype](https://github.com/google/pytype) – gilch Dec 19 '18 at 05:34
  • Isn't there also just a way to define your own custom type annotation like those in the `typing` library? I was sure the answer was yes, but I can't find how. Is it just a class which declares that all (`Iterable`s of )*`Garthok`s are its subclasses or something? – Daniel H Dec 19 '18 at 05:38
  • 1
    @DanielH classes are types. You can also use `TypeVars` and subclass `Generic` for that `[]` syntax. – gilch Dec 19 '18 at 05:42
  • @DanielH Yeah, I didn't see anything like that. But as a bonus, while perusing the typing documentation just now, I discovered the unrelated `@typing.overload` decorator, which I needed for something else :D – JesusFreke Dec 19 '18 at 05:46
  • You can generate test strategies for recursive types in the "hypothesis" module as follows https://hypothesis.readthedocs.io/en/latest/data.html#hypothesis.strategies.deferred – Reb.Cabin Nov 26 '19 at 21:11
  • 2
    Some types of forward references are handled by PEP0563. You can use them starting from Python 3.7 by doing `from __future__ import annotations` – Konstantin Dec 02 '20 at 16:03
  • 2
    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'`. – d-k-bo Oct 05 '21 at 20:53
  • @d-k-bo I used `from typing import Type; NestedStr = list[str | Type["NestedStr"]]` to solve this but when I try to do this: `def get_first(x: str | NestedStr) -> str: \ if isinstance(x, str): return x \ return get_first(x[0])` I got an error saying `Argument of type "str | NestedStr" cannot be assigned to parameter "x" of type "str | NestedStr" in function "get_first"` – Pablo LION Dec 23 '22 at 07:00
-1

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.