Типизации родительского метода в зависимости от реализации дочернего
Столкнулся сегодня с задачей типизации родительского метода в зависимости от реализации дочернего метода. Что я имею ввиду, есть у меня примерно такой класс:
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 шт):
Документация 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
.