Consider the following code:
from typing import TypeVar
import dataclasses
@dataclasses.dataclass
class A:
pass
@dataclasses.dataclass
class B(A):
pass
T = TypeVar("T", A, B)
def fun(
x1: T,
x2: T,
) -> int:
if type(x1) != type(x2):
raise TypeError("must be same type!")
if type(x1) == A:
return 5
elif type(x1) == B:
return 10
else:
raise TypeError("Type not handled")
fun(x1=A(), x2=A()) # OK
fun(x1=B(), x2=B()) # OK
fun(x1=B(), x2=A()) # Will throw TypeError, how can I get mypy to say this is an error?
fun(x1=A(), x2=B()) # Will throw TypeError, how can I get mypy to say this is an error?
Mypy is not seeing any problem here. It seems like it is always interpreting the passed object as a base class object of type A.
Is there a way to make the generic even more strict in the sense that it is sensitive to the exact class type? Such that if x1 is of type B, then also x2 must be exactly of type B? If x1 is of type A then also x2 must be exactly of type A?
This was a fun question - at first I considered solving it in the following way:
I thought this might be a quirk of the way
TypeVarworks initially, but I discovered that even if we specify in an overload, that it must beA, AorB, Bthen it still won't raise an error on the final two lines. The last two just use the overloadA, A, becauseA, Bis still a subtype ofA, A. Python does not distinguish at all between direct instances and subtypes - you could enforce a structural type with aProtocol, so long as there was a structural difference between A and B.Even if you made the function arg a
list[A], B's would still be valid in the list for this reason.If you're trying to get B to pick to up all the attributes of A, and have them be distinct types, I would instead do it this way, with A being now a hidden base class, and A2 exposed to an end user:
Hope this is useful!