Mypy: Problems using class type variable in class methods

Created on 1 Jul 2017  路  12Comments  路  Source: python/mypy

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.

feature needs discussion priority-0-high

Most helpful comment

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

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

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

Was this page helpful?
0 / 5 - 0 ratings