Given the following python code:
from typing import Protocol, TypeVar
class A: pass
class B(A): pass
class C(A): pass
T = TypeVar("T", B, C, contravariant=True)
class X(Protocol[T]):
def f(self, t: T) -> None: ...
class XTImpl(X[T]):
def f(self, t: T) -> None: pass
class XBImpl(X[B]):
def f(self, t: B) -> None: pass
How can I type a variable that accepts only a XTimpl
instance and not XBImpl
instances ?
My true use case is that I have functions
def fB(x: type[X[B]]): ...
def fC(x: type[X[C]]): ...
def fT(x):
fB(x)
fC(x)
And I would like to type argument x
in fT
so that only XTImpl
is accepted. fB
is expected to accept both XTImpl
and XBImpl
.
I would like something like just X
for instance (for which I get error "Expected type arguments for generic class").
If we changed T = TypeVar("T", B, C, contravariant=True)
into T = TypeVar("T", A, contravariant=True)
then we could just use X[A].
But in my true use case, B and C are not direct children of a common ancestor.
I can see why this caused you difficulty, the contravariant condition on T proved troublesome. The solution I have below is essentially a placeholder for an intersection type in fT. Since T is limited to only two values, we can use overloads to describe each available option:
@overload
def fT(x: type[XTImpl[B]]):
...
@overload
def fT(x: type[XTImpl[C]]):
...
def fT(x: type[XTImpl[Any]]):
fB(x)
fC(x)
The interior of the function fT has a slightly broader type than it should (note the use of Any here), but by overloading at least the exterior interior of fT should now be correct. Hope this helps!