Типизации родительского метода в зависимости от реализации дочернего

Столкнулся сегодня с задачей типизации родительского метода в зависимости от реализации дочернего метода. Что я имею ввиду, есть у меня примерно такой класс:

from abc import ABC, abstractmethod

class PageIterator(ABC):
    def __init__(self, last_page):
        self.current_page = 1
        self.last_page = last_page

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_page > self.last_page:
            raise StopIteration
        response = self.get()
        self.current_page += 1
        return response

    @abstractmethod
    def get(self):
        ...

Как думаю, понятно из контекста, класс представляет собой реализацию последовательного прохода по страницам какого-то сайта. При этом присутствует абстрактный метод get, реализация которого ложится на дочерние классы. В зависимости от реализации get способен возвращать разные объекты. Для примера, реализация будет следующей:

from dataclasses import dataclass

@dataclass()
class CurrentPage:
    page: int


@dataclass()
class LastPage:
    last_page: int


class MainPage(PageIterator):
    def get(self):
        return CurrentPage(self.current_page)


class CommentPage(PageIterator):
    def get(self):
        return LastPage(self.current_page)

Использование подразумевается примерно следующее:

for i in MainPage(100):
    print(i)

for j in CommentPage(100):
    print(j)

Код работает, однако проблема заключается в том, что инструменты статического анализа кода в IDE (я пробовал PyCharm нескольких версий и VSCode) не способны определить самостоятельно тип возвращаемого итератором значения и отображают Any. Логичным действием было бы добавить что-то вроде такого:

@abstractmethod
def get(self) -> Union[CurrentPage, LastPage]:
    ...

Или такого:

def __next__(self) -> Union[CurrentPage, LastPage]:

Подсказки появляются, однако типы возвращаемых значений в IDE перемешиваются (критично, если объекты большие и имеют много внутренних полей). Хотелось бы иметь для каждой реализации отдельную подсказку. На этом моменте моих навыков уже не хватает, единственное, до чего я додумался, это сделать __next__ тоже абстрактным и прописывать его вместе с подсказками типов в каждом дочернем классе, но это уже походит на дублирование кода, что не есть хорошо...


Ответы (1 шт):

Автор решения: Alexander Lonberg

Документация generics. Правда в python-овской документации не всегда все очевидно. Но их generics особенно ничем не отличаются от других языков, за исключением квадратных скобок [...].

class FooBase[T](ABC):
    @abstractmethod
    def some(self) -> T:
        pass

class Foo(FooBase[str]):
    def some(self) -> str:
        return "text"

Даже подсказки автозаполнения работать будут:

введите сюда описание изображения

В python_3.13 добавлены параметров типов по умолчанию [T:MyType = DefaultType], но пока эта альфа-версия почти нигде не поддерживается.

Ранее использовался дурацкий T = TypeVar('T'), но это уже устарело, ибо почти все перешли/переходят на python_3.12.

→ Ссылка