Follow up from a discussion on gitter:
from typing import *
T = TypeVar('T')
class Factory(Generic[T]):
@classmethod
def instance(cls) -> 'Factory[T]':
return cls()
def produce(self) -> T:
raise NotImplementedError()
@classmethod
def get(cls) -> T:
return cls.instance().produce()
class HelloWorldFactory(Factory[str]):
def produce(self) -> str:
return 'Hello World'
# factory.py:25: error: Incompatible return value type (got "T", expected "str")
def error_with_mypy() -> str:
return HelloWorldFactory.get()
@ilevkivskyi mentioned this should not error and that this code is valid. I don't have the necessary context to understand how to classify this issue.
I would simplify the example to show only the essential part:
class Factory(Generic[T]):
def produce(self) -> T:
...
@classmethod
def get(cls) -> T:
return cls().produce()
class HelloWorldFactory(Factory[str]):
def produce(self) -> str:
return 'Hello World'
reveal_type(HelloWorldFactory.get()) # mypy should be able to infer 'str' here
On one hand, type variables are bound on instances, not on classes, but in case of a non-generic subclass I think the type variable can be bound on that class. The problem however is that we need to give a clear error message when this is not possible, something like (using above example):
Factory.get() # E: cannot bind type variable on class (use an instance or a non-generic subclass)
Facrory[int].get() # E: cannot bind type variable on class (use an instance or a non-generic subclass)
Similar issues have come up previously, so this would perhaps be a reasonable feature to have eventually -- at least assuming the implementation isn't very complex.
The produce() method is also irrelevant to the example. Shorter yet self-contained:
from typing import Generic, TypeVar
T = TypeVar('T')
class Factory(Generic[T]):
@classmethod
def get(cls) -> T: ...
class HelloWorldFactory(Factory[str]): pass
reveal_type(HelloWorldFactory.get()) # mypy should be able to infer 'str' here
@JukkaL Do you have any thoughts on what it would take to fix this? It's blocking us here at Lyft.
@rowillia My recommendation is to use a regular method instead of a class method. Example:
from typing import Generic, TypeVar
T = TypeVar('T')
class Factory(Generic[T]):
def get(cls) -> T: ...
class _HelloWorldFactory(Factory[str]): pass
HelloWorldFactory = _HelloWorldFactory() # This singleton is mostly for convenience
reveal_type(HelloWorldFactory.get()) # this is 'str'
You can avoid the singleton by using HelloWorldFactory().get() instead.
I bumped into this bug today as well, and was surprised to see this comment:
type variables are bound on instances, not on classes
If this is the case, then why is
class G(Generic[T]):
@classmethod
def m(cls) -> 'G[T]':
...
not itself an error? being able to say G[T] at least implies to me that T is bound.
That method returns an instance of G[T].
According to reveal_type it returns an instance of G[<nothing>].
More specifically: G[int].m() returns G[<nothing>].
Oh, I think in this case it's basically equivalent to a top-level function returning G[T]: there is nothing to constrain the value of the typevar, so mypy infers "
@glyph
If this is the case, then why is ... not itself an error?
Because it is not, it may be bound in a subclass as said few word later in the comment you quote, there is even a simple example just few comments above https://github.com/python/mypy/issues/3645#issuecomment-313237283
@ilevkivskyi Thank you for fixing this issue!
Most helpful comment
The
produce()method is also irrelevant to the example. Shorter yet self-contained: