-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Problems using class type variable in class methods #3645
Comments
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 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 |
I bumped into this bug today as well, and was surprised to see this comment:
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 |
That method returns an instance of |
According to |
More specifically: |
Oh, I think in this case it's basically equivalent to a top-level function returning |
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 #3645 (comment) |
Fixes #3645 Fixes #1337 Fixes #5664 The fix is straightforward, I just add/propagate the bound type variable values by mapping to supertype. I didn't find any corner cases with class methods, and essentially follow the same logic as when we generate the callable from `__init__` for generic classes in calls like `C()` or `C[int]()`. For class attributes there are two things I fixed. First we used to prohibit ambiguous access: ```python class C(Generic[T]): x: T C.x # Error! C[int].x # Error! ``` but the type variables were leaking after an error, now they are erased to `Any`. Second, I now make an exception and allow accessing attributes on `Type[C]`, this is very similar to how we allow instantiation of `Type[C]` even if it is abstract (because we expect concrete subclasses there), plus this allows accessing variables on `cls` (first argument in class methods), for example: ```python class C(Generic[T]): x: T def get(cls) -> T: return cls.x # OK ``` (I also added a bunch of more detailed comments in this part of code.)
@ilevkivskyi Thank you for fixing this issue! |
Follow up from a discussion on gitter:
@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.
The text was updated successfully, but these errors were encountered: