Reputation: 20262
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 Iterable
s of Garthok
s? 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
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
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