Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mypy: is there some way to implement a 'SelfType' TypeVar?

so this is a rather minor issue but I was wondering if anyone more familiar with the typing module might know if there's a way to do this.

I'd like to be able to define a type variable that is always equivalent to an instance of the class it is used inside (and if used by a subclass, equivalent to an instance of that subclass type).

What I'm currently doing to ensure mypy understands that the return value is equivalent to the class it is called on is annotating the 'self' and 'cls' arguments like so:

from typing import TypeVar, Type

T = TypeVar("T")


class Parent:
    @classmethod
    def from_classmethod(cls: Type[T]) -> T:
        return cls()

    def from_method(self: T) -> T:
        return type(self)()


class Child(Parent):
    pass


Child.from_classmethod()  # mypy: Revealed type is Child
Child().from_method()  # mypy: Revealed type is Child

And it does work, mypy will interpret these correctly as being class Child, not Parent.

However, I don't want to do it this way if I don't have to. I was wondering if there's some way to create a TypeVar that works like this:

SelfType = ...  # some voodoo magic goes here


class Parent:
    @classmethod
    def from_classmethod(cls) -> SelfType:
        return cls()

    def from_method(self) -> SelfType:
        return type(self)()


class Child(Parent):
    pass


# OtherParent is reusing SelfType without having to redefine it
class OtherParent:
    @classmethod
    def from_classmethod(cls) -> SelfType:
        return cls()

    def from_method(self) -> SelfType:
        return type(self)()


class OtherChild(OtherParent):
    pass


Child.from_classmethod()  # mypy: Revealed type is Child
Parent.from_classmethod()  # mypy: Revealed type is Parent
OtherChild.from_classmethod()  # mypy: Revealed type is OtherChild
OtherParent.from_classmethod()  # mypy: Revealed type is OtherParent

The main point being that I only ever have to define this TypeVar once and then be able to apply it to any class I want and have mypy contextually infer that the type is the same as the class it's called from (even if the method gets inherited).

Is this at all possible to do on our end, or would this require a feature request in the mypy project?

like image 585
matthewgdv Avatar asked Jan 26 '23 18:01

matthewgdv


2 Answers

You have to define a typevar. See this discussion on the topic.

  T = TypeVar('T', bound='Copyable')
  class Copyable:
      def copy(self: T) -> T:
          # return a copy of self
like image 149
Mihai Andrei Avatar answered Jan 31 '23 08:01

Mihai Andrei


This is added by PEP 673 from Python 3.11: https://peps.python.org/pep-0673/

like image 40
Neil G Avatar answered Jan 31 '23 09:01

Neil G