Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
394 views
in Technique[技术] by (71.8m points)

python - Difference between TypeVar('T', A, B) and TypeVar('T', bound=Union[A, B])

I'm struggling to get my head around the difference between the following two TypeVars:

from typing import TypeVar, Union

class A: pass
class B: pass

T = TypeVar("T", A, B)
T = TypeVar("T", bound=Union[A, B])

Anyone want to enlighten me?


As an example of something I don't get: this passes type checking...

T = TypeVar("T", bound=Union[A, B])

class AA(A):
    pass


class X(Generic[T]):
    pass


class XA(X[A]):
    pass


class XAA(X[AA]):
    pass

...but with T = TypeVar("T", A, B), it fails with

error: Value of type variable "T" of "X" cannot be "AA"


Related: this question on the difference between Union[A, B] and TypeVar("T", A, B).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

When you do T = TypeVar("T", bound=Union[A, B]), you are saying T can be bound to either Union[A, B] or any subtype of Union[A, B]. It's upper-bounded to the union.

So for example, if you had a function of type def f(x: T) -> T, it would be legal to pass in values of any of the following types:

  1. Union[A, B] (or a union of any subtypes of A and B such as Union[A, BChild])
  2. A (or any subtype of A)
  3. B (or any subtype of B)

This is how generics behave in most programming languages: they let you impose a single upper bound.


But when you do T = TypeVar("T", A, B), you are basically saying T must be either upper-bounded by A or upper-bounded by B. That is, instead of establishing a single upper-bound, you get to establish multiple!

So this means while it would be legal to pass in values of either types A or B into f, it would not be legal to pass in Union[A, B] since the union is neither upper-bounded by A nor B.


So for example, suppose you had a iterable that could contain either ints or strs.

If you want this iterable to contain any arbitrary mixture of ints or strs, you only need a single upper-bound of a Union[int, str]. For example:

from typing import TypeVar, Union, List, Iterable

mix1: List[Union[int, str]] = [1, "a", 3]
mix2: List[Union[int, str]] = [4, "x", "y"]
all_ints = [1, 2, 3]
all_strs = ["a", "b", "c"]


T1 = TypeVar('T1', bound=Union[int, str])

def concat1(x: Iterable[T1], y: Iterable[T1]) -> List[T1]:
    out: List[T1] = []
    out.extend(x)
    out.extend(y)
    return out

# Type checks
a1 = concat1(mix1, mix2)

# Also type checks (though your type checker may need a hint to deduce
# you really do want a union)
a2: List[Union[int, str]] = concat1(all_ints, all_strs)

# Also type checks
a3 = concat1(all_strs, all_strs)

In contrast, if you want to enforce that the function will accept either a list of all ints or all strs but never a mixture of either, you'll need multiple upper bounds.

T2 = TypeVar('T2', int, str)

def concat2(x: Iterable[T2], y: Iterable[T2]) -> List[T2]:
    out: List[T2] = []
    out.extend(x)
    out.extend(y)
    return out

# Does NOT type check
b1 = concat2(mix1, mix2)

# Also does NOT type check
b2 = concat2(all_ints, all_strs)

# But this type checks
b3 = concat2(all_ints, all_ints)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...