Search code examples
pythonpython-typingunion-typestype-variables

What's the difference between a constrained TypeVar and a Union?


If I want to have a type that can be multiple possible types, Unions seem to be how I represent that:

U = Union[int, str] 

U can be an int or a str.

I noticed though that TypeVars allow for optional var-arg arguments that also seem to do the same thing:

T = TypeVar("T", int, str)

Both T and U seem to only be allowed to take on the types str and int.

What are the differences between these two ways, and when should each be preferred?


Solution

  • T's type must be consistent across multiple uses within a given scope, while U's does not.

    With a Union type used as function parameters, the arguments as well as the return type can all be different:

    U = Union[int, str]
    
    def union_f(arg1: U, arg2: U) -> U:
        return arg1
    
    x = union_f(1, "b")  # No error due to different types
    x = union_f(1, 2)  # Also no error
    x = union_f("a", 2)  # Also no error
    x # And it can't tell in any of the cases if 'x' is an int or string
    

    Compare that to a similar case with a TypeVar where the argument types must match:

    T = TypeVar("T", int, str)
    
    def typevar_f(arg1: T, arg2: T) -> T:
        return arg1
    
    y = typevar_f(1, "b")  # "Expected type 'int' (matched generic type 'T'), got 'str' instead
    y = typevar_f("a", 2)  # "Expected type 'str' (matched generic type 'T'), got 'int' instead
    
    y = typevar_f("a", "b")  # No error
    y  # It knows that 'y' is a string
    
    y = typevar_f(1, 2)  # No error
    y  # It knows that 'y' is an int
    

    So, use a TypeVar if multiple types are allowed, but different usages of T within a single scope must match each other. Use a Union if multiple types are allowed, but different usages of U within a given scope don't need to match each other.