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?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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.

Method 2

I want to add, as a consequence of what @Carcigenicate explained:

With Union, the operation you use between arguments has to be supported by all arguments in any permutation order:

from typing import Union

U = Union[int, str]

def add(a: U, b: U):
    return a + b

Here int + int and str + str is OK but not the int + str and str + int.
Mypy says:

main.py:6: error: Unsupported operand types for + ("int" and "str")
main.py:6: error: Unsupported operand types for + ("str" and "int")

If we change + to *: int * str and str * int and int * int is OK but Mypy doesn’t like str * str:

from typing import Union

U = Union[int, str]

def add(a: U, b: U):
    return a * b

Mypy says:

main.py:6: error: Unsupported operand types for * ("str" and "str")

If we change U = Union[int, str] to U = Union[int, float] with above tests, it accepts. All four cases are acceptable.

Here we use TypeVar instead to get rid of those complains, the T is the same, either int + int or str + str:

from typing import TypeVar

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

def add(a: T, b: T) -> T:
    return a + b


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x