How to check if a type annotation represents an union

I’m trying to use type annotations at runtime. For this purpose, I need to check if a type annotation is for an optional value (Optional[T] or T | None). Optional[T] is just little more than syntax sugar for Union[T, None], thus the first problem to solve is to check if the type annotation is an union. However does not appear to be an easy way to do that. Wanting to support Python back to version 3.8, the only solution I’ve found is this:

if sys.version_info < (3, 9):
    def _is_union(x):
        origin = typing.get_origin(x)
        return isinstance(origin, typing._SpecialForm) and origin._name == 'Union'
elif sys.version_info < (3, 10):
    def _is_union(x):
        return isinstance(x, typing._UnionGenericAlias)
else:
    def _is_union(x):
        return isinstance(x, (type(int | None), typing._UnionGenericAlias))

Is there an simpler way? Other than for type checking with mypy I haven’t been working with annotations much, thus I may be missing something obvious.

Something like this:

if sys.version_info >= (3, 10):
    _UNION_TYPES = {typing.Union, types.UnionType}
else:
    _UNION_TYPES = {typing.Union}
def is_union(x):
    return typing.get_origin(x) in _UNION_TYPES

Generally, get_origin() and get_args() are very useful for introspection. It should not be necessary to use private objects in the typing module, like _UnionGenericAlias and _SpecialForm.

1 Like

Thanks! I started looking in that direction, but the fact that typing.Union cannot be use in an instance check threw me off.