How do I type hint a function that returns an instance of the current class?

887 views Asked by At

Let's say I have these classes:

class GenericCopyable:
    def copy(self) -> GenericCopyable:
        ... # whatever is required to copy this

class CopyableFoo(GenericCopyable):
    ... # uses the parent implementation of "copy"
    def bar(self): …

def some_code(victim: CopyableFoo):
    v = victim.copy()
    v.bar()  ### I know that this works because "v" is a CopyableFoo, but mypy doesn't

The problem is that I need the return type of CopyableFoo.copy() to be CopyableFoo, not GenericCopyable.

Is that possible?

Edited: The above is sample code to illustrate the problem. Modifying some_code or CopyableFoo in some way is certainly possible in this example; in my "real" program that'd be substantially more difficult.

3

There are 3 answers

0
Axe319 On BEST ANSWER

You could do this.

from typing import TypeVar
# We define T as a TypeVar bound to the base class GenericCopyable
T = TypeVar('T', bound='GenericCopyable')

class GenericCopyable:
    # we return the type T of the type of self
    # Basically returning an instance of the calling
    # type's class
    def copy(self: T) -> T:
        return type(self)()

class CopyableFoo(GenericCopyable):
    pass

foo = CopyableFoo()

bar = foo.copy()
print(bar)

This looks a little clumsy because typically we don't need to annotate self, since it's implicitly a type of the class it's bound to. However, mypy seems to be ok with it.

0
mightyandweakcoder On

One possible solution is to override the method in your children class and then call your superclass method with the children class method specifying the return type of their own instance.

class GenericCopyable:
    def copy(self) -> GenericCopyable:
        ... # whatever is required to copy this

class CopyableFoo(GenericCopyable):
   def copy(self)->CopyableFoo:
       return super().copy()

Another possible solution is by using the typing module to import Union. This specifies the function in your parent class is able to return multiple types


from typing import Union

class GenericCopyable:
    def copy(self) -> Union[GenericCopyable,CopyableFoo]:
        ... # whatever is required to copy this

class CopyableFoo(GenericCopyable):
    #Call parent class method directly
    GenericCopyable.copy()

0
Arthur Gordon-Wright On

Since Python 3.11, the standard library contains an explicit special type for this - Self.

This replaces the TypeVar approach suggested in @Axe319's answer - note that the docs referenced above explicitly mention this.

The base class with Self would be written like this:

from typing import Self

class GenericCopyable:
    def copy(self) -> Self:
        ...

This specifies to type checkers that any instance of GenericCopyable returns an instance of the same type as itself from its copy() method.