Skip to content

@classmethod of Generic class #1337

Closed

Description

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

Activity

  1. gvanrossum commented on Apr 6, 2016

    @gvanrossum
    Member

    @rwbarton Another typevar mystery you might look into?

  2. added this to the milestone on Apr 7, 2016
  3. JukkaL commented on Apr 14, 2016

    @JukkaL
    Collaborator

    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.

  4. modified the milestones: , on Apr 14, 2016
  5. rwbarton commented on Apr 14, 2016

    @rwbarton
    Contributor

    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.

  6. gvanrossum commented on Apr 14, 2016

    @gvanrossum
    Member
  7. rwbarton commented on Apr 14, 2016

    @rwbarton
    Contributor

    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.

  8. gvanrossum commented on Apr 14, 2016

    @gvanrossum
    Member

    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.

  9. tharvik commented on Apr 15, 2016

    @tharvik
    ContributorAuthor

    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.

  10. JukkaL commented on Apr 15, 2016

    @JukkaL
    Collaborator

    @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' (#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.

  11. methane commented on Jun 2, 2016

    @methane
    Member

    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

  12. rwbarton commented on Jun 29, 2016

    @rwbarton
    Contributor

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

  13. added a commit that references this issue on Jun 29, 2016
    7e610b7
  14. JukkaL commented on Jun 29, 2016

    @JukkaL
    Collaborator

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

  15. removed this from the milestone on Mar 29, 2017
  16. elazarg commented on Oct 19, 2017

    @elazarg
    Contributor

    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: ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions