(python 3.10.6, mypy 0.990)
The following examples are all accepted by mypy:
from typing import Generic, TypeVar
T = TypeVar('T')
class Maybe(Generic[T]):
def __init__(self, val: T):
self._val = val
@classmethod
def empty(cls):
return cls(None)
from typing import Generic, TypeVar
U = TypeVar('U')
V = TypeVar('V')
class Example(Generic[U, V]):
def __init__(self, a: U, b: V):
self._a = a
self._b = b
@classmethod
def empty(cls):
return cls(None, None)
from typing import Generic, TypeVar
U = TypeVar('U')
V = TypeVar('V')
class Example(Generic[U, V]):
def __init__(self, a: U, b: V):
self._a = a
self._b = b
@classmethod
def both(cls, val: U, b: V):
return cls(val, b)
But this case returns the following error: error: Argument 2 to "Example" has incompatible type "None"; expected "V" [arg-type]
from typing import Generic, TypeVar
U = TypeVar('U')
V = TypeVar('V')
class Example(Generic[U, V]):
def __init__(self, a: U, b: V):
self._a = a
self._b = b
@classmethod
def first(cls, val: U):
return cls(val, None)
It seems that mypy accepts binding generic inputs to typevars, accepts binding concrete values to infer typevars, but doesn't permit a mix of binding and inferring typevars. What's going on?
CodePudding user response:
tl;dr what if I call type(Example(0, 0)).first(0)
? We can't infer cls
as type[Example[int, None]]
in that case.
A rambling explanation
It's typically not type-safe to call a class in Python. The first two examples actually allow type errors because I can subclass them and change the __init__
. This type checks but fails at runtime:
class Maybe1(Maybe[int]):
def __init__(self):
super().__init__(0)
Maybe1.empty()
OK, so what if we make Maybe
@final
? Then Maybe1
can't be defined and the problem goes away. Or does it? We'll come back to this.
Regarding Example
, it's not safe to call cls
if it's typed as the enclosing class. So we can either call Example
class Example(Generic[U, V]):
def __init__(self, a: U, b: V):
self._a = a
self._b = b
@classmethod
def first(cls, val: U) -> Example[U, None]:
return Example(val, None)
or type cls
as a function
class Example(Generic[U, V]):
def __init__(self, a: U, b: V):
self._a = a
self._b = b
@classmethod
def first(
cls: Callable[[U, None], Example[U, None]],
val: U
) -> Example[U, None]:
return cls(val, None)
Obviously neither of these will work if you want a subclass's Example1.first
to produce an Example1
. I think Python's lack of HKTs prohibits typing that properly. It may look something like
# not valid Python
E = TypeVar("E", bound=Example)
class Example(Generic[U, V]):
def __init__(self, a: U, b: V):
self._a = a
self._b = b
@classmethod
def first(cls: Callable[[U, None], E[U, None]], val: U) -> E[U, None]:
return cls(val, None)
I don't believe Self
in Python 3.11 helps here
we reject using Self with type arguments
We mentioned @final
, so what if we @final
Example
?
@final
class Example(Generic[U, V]):
def __init__(self, a: U, b: V):
self._a = a
self._b = b
@classmethod
def first(cls, val: U) -> Example[U, None]:
return cls(val, None)
Here's where we recover your error. It's reasonable to ask Python to infer Example
in Example.first(0)
to be Example[int, None]
, but what if we do type(Example(0, 0)).first(0)
? Then we can't infer type(Example(0, 0))
as Example[int, None]
, so we can't infer as Example[int, None]
in all cases.