I\'m struggling to get my head around the difference between the following two TypeVars
from typing impor
After a bunch of reading, I believe mypy correctly raises the type-var error in the OP's question:
generics.py:31: error: Value of type variable "T" of "X" cannot be "AA"
See the below explanation.
Second Case: TypeVar("T", bound=Union[A, B])
I think @Michael0x2a's answer does a great job of describing what's happening. See that answer.
First Case: TypeVar("T", A, B)
The reason boils down to Liskov Substitution Principle (LSP), also known as behavioral subtyping. Explaining this is outside the scope of this answer, you will need to read up on + understanding the meaning of invariance vs covariance.
From python's typing docs for TypeVar:
By default type variables are invariant.
Based on this information, T = TypeVar("T", A, B) means type variable T has value restrictions of classes A and B, but because it's invariant... it only accepts those two (and not any child classes of A or B).
Thus, when passed AA, mypy correctly raises a type-var error.
You might then say: well, doesn't AA properly match behavioral subtyping of A? And in my opinion, you would be correct.
Why? Because one can properly substitute out and A with AA, and the behavior of the program would be unchanged.
However, because mypy is a static type checker, mypy can't figure this out (it can't check runtime behavior). One has to state the covariance explicitly, via the syntax covariant=True.
Also note: when specifying a covariant TypeVar, one should use the suffix _co in type variable names. This is documented in PEP 484 here.
from typing import TypeVar, Generic
class A: pass
class AA(A): pass
T_co = TypeVar("T_co", AA, A, covariant=True)
class X(Generic[T_co]): pass
class XA(X[A]): pass
class XAA(X[AA]): pass
Output: Success: no issues found in 1 source file
So, what should you do?
I would use TypeVar("T", bound=Union[A, B]), since:
A and B aren't relatedFurther reading on LSP-related issues in mypy: