Let's say I have some type aliases, maybe
Point3D = Annotated[tuple[float, float, float], "A 3D Point"]
Points = Annotated[list[Point3D, list[float]], "A collection of points"]
If I try to print Points, I get
typing.Annotated[list[typing.Annotated[tuple[float, float, float], 'A 3D Point'] | list[float]], 'A collection of points']
I want to only get the list[tuple[float, float, float]]
part. I tried using typing.get_args(Points)[0]
but that just gives this:
list[typing.Annotated[tuple[float, float, float], 'A 3D Point'] | list[float]]
Where there is still the unwanted 'A 3D Point'
. How can I achieve this? I tried replacing it with the ", '.*?'
regex, but that didn't work, and I'm not experienced enough with regex to be able to figure out why.
Note:
I can't just change all the Annotated
types to normal type hints because I still need that annotation content to be displayed elsewhere.
The solution is simple: Walk the tree (at runtime) and convert all Annotated
nodes to their bare-type counterpart. No regex needed.
from typing import Annotated, get_args, get_origin
def convert_annotated_to_bare_types(type_object: type):
# For a given `X[Y, Z, ...]`:
# * `get_origin` returns `X`
# * `get_args` return `(Y, Z, ...)`
# For non-supported type objects, the return values are
# `None` and `()` correspondingly.
origin, args = get_origin(type_object), get_args(type_object)
if origin is None:
# No origin -> Not a generic/no arguments.
return type_object
if origin is Annotated:
# Annotated -> Convert the first argument recursively.
bare_type = get_args(type_object)[0]
return convert_annotated_to_bare_types(bare_type)
# Otherwise, it is a generic. Convert all arguments recursively.
converted_args = [
convert_annotated_to_bare_types(arg) for arg in args
]
return origin[*converted_args]
It works exactly like you would expect, even if list
is not supposed to take 2 type arguments:
>>> convert_annotated_to_bare_types(Points)
list[tuple[float, float, float], list[float]]