'Infer type from subclass method return type

I'm trying to infer a type from the returned type of a subclass method. So far I haven't been able to make this work without making Parent a generic class (which I'd like to avoid).

The Parent class will be in a lib to be imported and create subclasses of.

Any idea how to do that ? Thanks a lot.

from __future__ import annotations
from typing import Any, Iterable, Optional, TypeVar, Type

TRetType = TypeVar("TRetType")
TChild = TypeVar("TChild", bound="Parent")


class Parent:
    def run(self) -> Any:
        return "foo"

    @classmethod
    def run_children(cls: Type[TChild], children: Iterable[TChild]) -> Iterable[TRetType]:
        for child in children:
            ret: TRetType = child.run() # <--- attempt to force TRetType to the return type of child.run(), in that case, int
            yield ret


class Child(Parent):
    def run(self) -> int: # <--- the type that children_res should be of
        return 1


c = [Child() for _ in range(3)]
children_res = Child.run_children(c) # <--- children_res has no type


Solution 1:[1]

I don't think that's possible; there's no way to infer the return type of run from just the TChild type var.

I assume that you don't want to make Parent generic because there's no higher-kinded types in Python? I imagine what you'd ideally do is something like:

T = TypeVar("T")
R = TypeVar("R")
TChild = HigherKindedTypeVar("TChild", bound="Parent")


class Parent(Generic[T]):
    def run(self) -> T:
        raise NotImplementedError

    @classmethod
    def run_children(cls: Type[TChild[R]], children: Iterable[TChild[R]]) -> Iterable[R]:
        ...


class Child(Parent[int]):
    def run(self) -> int: # <--- the type that children_res should be of
        return 1

(Note that the above is pseudocode)


Without higher-kinded types, you'd have to either enforce the TChild constraint (so that children's elements are the same type as the class), or the return type constraint (so childrens's elements can be any Parent subclass, as long as they return the given type). You can choose which to enforce depending on your use case.

Here's an example of the latter (mypy-play link):

T = TypeVar("T")
R = TypeVar("R")

class Parent(Generic[T]):
    def run(self) -> T:
        raise NotImplementedError

    @classmethod
    def run_children(cls: Type[Parent[R]], children: Iterable[Parent[R]]) -> Iterable[R]:
        # Use `R` because `T` is already bound in class scope.
        ...

class IntChild1(Parent[int]):
    def run(self) -> int:
        return 1

class IntChild2(Parent[int]):
    def run(self) -> int:
        return 2

class StrChild(Parent[str]):
    def run(self) -> str:
        return "str"

IntChild1.run_children([IntChild1()])
IntChild1.run_children([IntChild2()])
IntChild1.run_children([StrChild()])  # error

IntChild2.run_children([IntChild1()])
IntChild2.run_children([IntChild2()])
IntChild2.run_children([StrChild()])  # error

StrChild.run_children([IntChild1()])  # error
StrChild.run_children([IntChild2()])  # error
StrChild.run_children([StrChild()])

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zecong Hu