Mypy: `@classmethod` of `Generic` class

Created on 6 Apr 2016  路  12Comments  路  Source: python/mypy

from typing import TypeVar, Generic

T = TypeVar('T')


class A(Generic[T]):
    @classmethod
    def func(cls) -> T:
        pass


class B(A['B']):
    pass


def except_b(b: B):
    pass


except_b(B.func())

is a valid python3 code, but mypy warns that

file:20: error: Argument 1 to "except_b" has incompatible type "T"; expected "B"

but the resolved pseudo-code of class B should be

class B(A['B']):
    @classmethod
    def func(cls) -> 'B':
        pass

if I replace it, mypy doesn't complain anymore.

I'm might have overlook some specificities of @classmethod but I think that func is also bind to class B thus should be type checking.

bug priority-1-normal

All 12 comments

@rwbarton Another typevar mystery you might look into?

Actually it seems that the signature of A.func should be rejected, since T is not defined at class/static method level, since the type variable is specific to an instance, and we have no self. Type variables should not be in scope for static or class methods. I'm going to move this to 0.4 since this seems like a pretty rare edge case.

It also seems consistent to turn the class typevars into function typevars for a static method call, like we do for constructors. We could give A.func the type def [T] () -> T, and B.func the type def () -> B. Hard to say whether that is useful without some realistic examples.

That would seem useful for constructors. I can't think of any off the bat
but I betcha there are some classes that take AnyStr and have a handy
alternate constructor that's a class or static method.

This is also an issue for class attributes:

class A(Generic[T]):
    a = []  # type: List[T]
class B(A[int]): pass

What is the type of B.a? For that matter what is the type of A.a?

This issue is actually more severe, because on the one hand we cannot give A.a any sort of generic type (only functions can be generic in mypy currently, and besides it would not be type safe to give A.a a type like forall T. List[T], as it is a mutable variable); and on the other hand the style of declaring class variables in order to specify the types of instance variables is used frequently, at least in mypy itself, and if we want to do the same in generic classes, we need to be able to make declarations like that of A.

Yeah, but mypy's handling of class attributes is just really poor, and the
meaning of that example isn't clear, because we do type erasure -- at
runtime there's only one A and it has only one a.

OTOH for classmethod/staticmethod it makes sense to be generic? Well I
guess interpreting them as generic _functions_ works too, so actually Jukka
is right about that. Sorry.

Hard to say whether that is useful without some realistic examples.

The issue was encounter when I wanted to force a subclass to implement a method to return an instance of the subclass (and this way, force a constructor).

If you want to have some code example (the main idea is to parse XML from nexpose API), it is the XmlParse class which force his childs to have the from_xml method. You can see how it used in the Message class.

@tharvik I'm not sure if your example is possible to be fully annotated via PEP 484, as type variables are specific to instances. Currently having an Any return type in the XmlParse class seems like the only option. If/when mypy will support 'self types' (https://github.com/python/mypy/issues/1212), they might be helpful in your use case.

@rwbarton If a class-level variable has a type based on a type variable, mypy should enforce that this variable should only be accessed via an instance, not via the class object. If we add that restriction, things should be fine.

Having generic class methods and static methods is fine, but they should perhaps use a separate type variable, not a class-level type variable. However, we could implicitly understand a class-level type variable as a method-level one if used in a static or class method, but that might be a little confusing.

Here is another realistic example.
https://gist.github.com/methane/d18c66d38b1916e7c36883c0a83f0a01

This demonstrates "Repository Pattern". [1]
Since repository is singleton for many cases, I want to implement it by classmethod.

[1] https://msdn.microsoft.com/en-us/library/ff649690.aspx

@JukkaL, is there anything wrong with continuing to turn class type variables that appear in class methods into function type variables, like we do now (and very similar to how the signature of __init__ is treated)?

What I'm going to implement is just mapping class type variables from the superclass before turning them into function type variables (as already happens for __init__). I think this will allow the above use case.

@rwbarton I think it's fine to turn class type variables into function type variables, but if we make this a real feature we should test that there is unlikely to be confusion (by mypy) between two different kinds of type variables in a single scope, as discussed above.

This should be possible to solve with a slightly better unification for self-type (currently it does only the minimal effort).

class A(Generic[T]):
    @classmethod
    def func(cls: Type[A[Q]]) -> Q: ...
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ilevkivskyi picture ilevkivskyi  路  25Comments

tjltjl picture tjltjl  路  35Comments

msullivan picture msullivan  路  26Comments

snakescott picture snakescott  路  27Comments

glyph picture glyph  路  26Comments