Uniform way to stringify a type

I’m looking for a uniformly nice way to achieve “stringification” of some basic types, including some generics.

For things like union types, the str(...) works well, but this also gives <class 'float'> for instance. Using getattr(t, '__name__', str(t)) covers both cases but dict[str].__name__ is dict, so I basically end up with this. Is there a better way?

def stringify_type(x):
    if hasattr(x, '__args__'):
        return str(x)
    return getattr(x, '__name__', str(x))

type_list = [
    float,
    dict,
    float | int,
    dict[str, int],
    tuple[int, float],
    tuple[tuple[int, int], int],
]

for t in type_list:
    print(stringify_type(t))

Expected result:

float
dict
float | int
dict[str, int]
tuple[int, float]
tuple[tuple[int, int], int]

A sidenote, I noticed that an IPython REPL will print these types “nicely” although I don’t quite understand how they did it. (A normal Python REPL will show float as <class 'float'>)

In [1]: float
Out[1]: float

In [2]: dict
Out[2]: dict

In [3]: float | int
Out[3]: float | int

In [4]: dict[str, int]
Out[4]: dict[str, int]

In [5]: tuple[int, float]
Out[5]: tuple[int, float]

I’m not sure what “better” means exactly, but you can use logic similar to this internal-only function in typing.py: https://github.com/python/cpython/blob/403886942376210662610627b01fea6acd77d331/Lib/typing.py#L231

Thank you the link was very helpful. A more robust way to cover the above use cases turns out to be:

def stringify_type(x):
    if isinstance(x, type)
        return x.__name__
    return repr(x)

So the deeper issue is that instances of types.GenericAlias, types.UnionType behave different from true types (instances of type) in that semantically repr(generic_type) \approx actual_type.__name__ but repr(generic_type) \neq repr(actual_type).

I’m also interested in this, as when writing a codegen tool, in some cases I’d like to have the user passing the type itself in some function, and I can output a stringified version that is valid Python syntax.

The provided solutions here seems to work, except for Generic, i.e. repr(Generic[T) gives typing.Generic[~T].

As of 2022, I had only found one library that offers type stringification: pytypes.type_str

More details in this presentation I gave at that time.