Skip to content

Problems using class type variable in class methods #3645

Closed

Description

@LiraNuna

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.

Activity

  1. ilevkivskyi commented on Jul 1, 2017

    @ilevkivskyi
    Member

    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)
  2. JukkaL commented on Jul 3, 2017

    @JukkaL
    Collaborator

    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.

  3. changed the title [-]"Incompatible return value" error for classmethod generic subclasses[/-] [+]Problems using class type variable in class methods[/+] on Jul 3, 2017
  4. gvanrossum commented on Jul 5, 2017

    @gvanrossum
    Member

    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
  5. rowillia commented on Feb 24, 2018

    @rowillia
    Contributor

    @JukkaL Do you have any thoughts on what it would take to fix this? It's blocking us here at Lyft.

  6. JukkaL commented on Feb 26, 2018

    @JukkaL
    Collaborator

    @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.

  7. glyph commented on Apr 18, 2018

    @glyph

    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.

  8. JelleZijlstra commented on Apr 18, 2018

    @JelleZijlstra
    Member

    That method returns an instance of G[T].

  9. glyph commented on Apr 18, 2018

    @glyph

    According to reveal_type it returns an instance of G[<nothing>].

  10. glyph commented on Apr 18, 2018

    @glyph

    More specifically: G[int].m() returns G[<nothing>].

  11. JelleZijlstra commented on Apr 18, 2018

    @JelleZijlstra
    Member

    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 "". I believe there's a separate issue about that.

  12. ilevkivskyi commented on Apr 18, 2018

    @ilevkivskyi
    Member

    @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 #3645 (comment)

  13. LiraNuna commented on Feb 24, 2019

    @LiraNuna
    Author

    @ilevkivskyi Thank you for fixing this issue!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions