Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python typing signature for instance of subclass?

Consider:

from __future__ import annotations

class A:
    @classmethod
    def get(cls) -> A:
        return cls()

class B(A):
    pass

def func() -> B: # Line 12
    return B.get()

Running mypy on this we get:

$ mypy test.py
test.py:12: error: Incompatible return value type (got "A", expected "B")
Found 1 error in 1 file (checked 1 source file)

Additionally, I have checked to see if old-style recursive annotations work. That is:

# from __future__ import annotations

class A:
    @classmethod
    def get(cls) -> "A":
# ...

...to no avail.

Of course one could do:

from typing import cast

def func() -> B: # Line 12
    return cast(B, B.get())

Every time this case pops up. But I would like to avoid doing that.

How should one go about typing this?

like image 546
webelo Avatar asked Aug 08 '26 12:08

webelo


1 Answers

The cls and self parameters are usually inferred by mpyp to avoid a lot of redundant code, but when required they can be specified explicitly by annotations.

In this case the explicit type for the class method would look like the following:

class A:
    @classmethod
    def get(cls: Type[A]) -> A:
        return cls()

So what we really need here is a way to make Type[A] a generic parameter, such that when the class method is called from a child class, you can reference the child class instead. Luckily, we have TypeVar values for this.

Working this into your existing example we will get the following:

from __future__ import annotations

from typing import TypeVar, Type


T = TypeVar('T')


class A:
    @classmethod
    def get(cls: Type[T]) -> T:
        return cls()


class B(A):
    pass


def func() -> B:
    return B.get()

Now mypy should be your friend again! 😎

like image 190
flakes Avatar answered Aug 11 '26 17:08

flakes



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!