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]
Answer from gilch on Stack OverflowYou 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]
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.
Generic `typing.ForwardRef` to support generic recursive types - Ideas - Discussions on Python.org
Recursive Generic Type Support
Recursive type hints
Recursive Generic Type Hints (python 3.12)
I have lists within lists with integer items (or bytes, but for example purposes ints are enough), for example: [1, 2, [3, 4], 5]
How can I go about creating a type hint for this structure?
This is what I've made:
from typing import List, Union NestedList = List[Union[int, 'NestedList']] test: NestedList = [1, 2, [3], 4] test.append([5]) test = test[-1]
But mypy fails with:
test_recursive_hints.py:7: error: Incompatible types in assignment (expression has type "int | NestedList", variable has type "NestedList") [assignment]
How to solve this problem?
TIL from this video typing a recursive flatten (by YT channel anthonywritescode) that you can now type hint recursive data & functions with generic type parameter!
# new syntax
# recursive type for nested list having elems of same type (eg. int)
type _RList[U] = list[U | _RList[U]]
def flatten[T](lst: _RList[T]) -> _RList[T]:
""" Flatten nested list.""""
return [
flatten(x) if isinstance(x, list) else x
for x in lst
]NOTE: Latest mypy type checks this new syntax, but editor / IDE may not recognize it yet.
Did you all know about this? Have you found more such cool type hinting syntax in Python?
As of mypy 0.990, mypy finally supports recursive type annotations, using the natural syntax:
from typing import Union, Dict, List
JSONVal = Union[None, bool, str, float, int, List['JSONVal'], Dict[str, 'JSONVal']]
d: JSONVal = {'a': ['b']}
mypy output:
Success: no issues found in 1 source file
Before 0.990, this would produce an error reporting a lack of recursive type support:
$ mypy asdf.py
asdf.py:3: error: Recursive types not fully supported yet, nested types replaced with "Any"
On such versions, Dict[str, Any] would be the way to go.
You can also use mutually recursive type aliases now, so you can do things like
from typing import Union, Dict, List
JSONVal = Union[None, bool, str, float, int, 'JSONArray', 'JSONObject']
JSONArray = List[JSONVal]
JSONObject = Dict[str, JSONVal]
d: JSONObject = {'a': ['b']}
Support for recursive types is now in Mypy.
As of October 2022 the implementation is provisional. You can enable it by adding the enable_recursive_aliases = true flag to pyproject.toml.
Starting from version 0.990 this will be enabled by default. Source.
Since Python is dynamically typed, there is no issue defining whatever classes you need.
class Tree:
left = None
right = None
def __init__(self, left, right):
self.left = left
self.right = right
Even if you are interested in typing these definitions, you can do that like in any other class-based object oriented language:
from typing import Union
class Tree:
left: Union['Tree', int]
right: Union['Tree', int]
def __init__(self, left: Union['Tree', int], right: Union['Tree', int]) -> None:
self.left = left
self.right = right
Note the use of strings for the name of the type (which you can avoid in more recent Python versions).
See this open issue in mypy for direct recursive algebraic types such as
Tree = Union[Tuple['Tree', 'Tree'], int]
The most common (though not necessarily recommended) way of defining the WordTree you describe is using a superclass and a shallow hierarchy:
from typing import List, final
class WordTree: pass
@final
class Word(WordTree):
word: str
@final
class Subword(WordTree):
subword: str
children: List[WordTree]
@final
class Root(WordTree):
children: List[WordTree]
Using such an implementation might require using isinstance checks (though Python3.10 gives you nice sugar for those). Constructors are omitted in this example to avoid clutter; you might want to use dataclass to get them, and other kinds of behavior, easily.
To date, Python gives you no way to disallow unrelated classes from inheriting from WordTree, thus breaking some of the ability to statically reason about such programs.
Some other OOP languages, such as Scala and Kotlin and (soon) Java, can take such a definition (using sealed classes) and give you type checks and syntactic constructs that are similar to the ones given by functional languages such as Haskell.
For all I know, this kind of design is usually recommended only for pure-data classes, such as ASTs. It is less suited for defining user-facing container such as trie, since it exposes the inner workings of the data structure. So even if you go with that design, you might want to use it as an implementation detail, and use another class, Trie, to be used by client code through a well-defined API. That class can have a WordTree field, or any other way of implementing the same logic.
IMO this is essential to how object-oriented design differs from functional design. The latter focuses on data flow and on static reasoning, whereas the former focuses on APIs, extensibility and decoupling. I think this is helpful to note, when porting between languages and environments - though as noted above, some languages try to enable both design approaches.
Here's an equivalent implementation of the Haskell binary tree in Python 3.10. Static type checking can be done with mypy.
from __future__ import annotations
from dataclasses import dataclass
from typing import Generic, TypeVar
T = TypeVar("T")
@dataclass
class Branch(Generic[T]):
value: T
left: Tree[T]
right: Tree[T]
@dataclass
class Leaf(Generic[T]):
value: T
Tree = Branch[T] | Leaf [T]
You can use it like this (note the pattern-matching in the contains function - a new feature of Python 3.10):
def contains(tree: Tree[T], value: T):
match tree:
case Leaf(x):
return x == value
case Branch(x, left, right):
return x == value or contains(left, value) or contains(right, value)
tree = Branch(
1,
Branch(2, Leaf(3), Leaf(4)),
Branch(5, Leaf(6), Branch(4, Leaf(7), Leaf(8)))
)
assert contains(tree, 1)
assert contains(tree, 5)
assert contains(tree, 8)
To implement your WordTree, you would do the following:
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class Word:
value: str
@dataclass
class Subword:
value: str
trees: list[WordTree]
@dataclass
class Root:
trees: list[WordTree]
WordTree = Word | Subword | Root
A note on the imports:
from __future__ import annotationsallows you to annotate with the name of a type that hasn't been defined yet.@dataclassautomatically defines a constructor for you.