Выполнять универсальные функции на определенной типизированной конечной точке REST, сохраняя типы
Я делаю типизированную библиотеку REST, где у всех конечных точек есть определенные классы и их методы установлены на объекте. Скажем, у нас есть список строк, возвращаемых конечной точкой A
, это будет иметь класс MVCE A
ниже. Я добавляю методы, которые должны работать на всех конечных точках в Base
Класс так, чтобы конечные точки включали в себя как можно меньше шаблонов.
Однако есть некоторые функции, которые мне нужно выполнить на всех конечных точках списка, такие как A
а также B
ниже, но не C
, Эта общая функция get_all
, так что мы получаем все объекты из списка.
Проблема в том, что у меня работает код, однако PyCharm и mypy не знают тип a
или же b
и говорит, что тип List[T]
это имеет смысл, так как я не уточнил, что T
является.
Как я могу сделать a
иметь тип List[str]
, а также b
иметь тип List[int]
?
_mock_a = list('abcdefghijklmnopqrstuvwxyz')
_mock_b = [int(i) for i in '12345678901234567890123456']
from typing import TypeVar, Callable, List
T = TypeVar('T')
class Base:
def pipe(self, fn: Callable[['Base'], List[T]]) -> List[T]:
return fn(self)
class A(Base):
def get(self, index=0, count=5) -> List[str]:
return _mock_a[index:index+count]
def count(self) -> int:
return len(_mock_a)
class B(Base):
def get(self, index=0, count=5) -> List[int]:
return _mock_b[index:index+count]
def count(self) -> int:
return len(_mock_b)
class C(Base):
def other(self) -> None:
pass
def get_all(base: Base) -> List[T]:
step = 5
return [
item
for start in range(0, base.count(), step)
for item in base.get(start, step)
]
# Has type List[T], but I want it to have List[str]
a = A().pipe(get_all)
print(a)
# Has type List[T], but I want it to have List[int]
b = B().pipe(get_all)
print(b)
Я пытался исправить это, но ни один из них не сработал.
class Method(Generic[T]):
@staticmethod
def get_all(base: Base) -> List[T]:
step = 5
return [
item
for start in range(0, base.count(), step)
for item in base.get(start, step)
]
a = A().pipe(Method[str].get_all)
print(a)
class Base:
def pipe(self, t: Type[T], fn: Callable[['Base'], T]) -> T:
return fn(self)
a = A().pipe(List[str], get_all)
print(a)
Я нашел способ получить вторую работу, которая работает как typing.cast
:
class Base:
def pipe(self, fn: Callable[['GetableEndpoint[T]'], List[T]], t: Type[T]=T) -> List[T]:
return fn(cast(GetableEndpoint[T], self))
class GetableEndpoint(Generic[T], Base, metaclass=abc.ABCMeta):
@classmethod
def __subclasshook__(cls, C):
if cls is GetableEndpoint:
if any('get' in B.__dict__ for B in C.__mro__) and any('count' in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
@abc.abstractmethod
def get(self, index=0, count=5) -> List[T]:
raise NotImplementedError()
@abc.abstractmethod
def count(self) -> int:
raise NotImplementedError()
def get_all(base: GetableEndpoint[T]) -> List[T]:
step = 5
return [
item
for start in range(0, base.count(), step)
for item in base.get(start, step)
]
a = A().pipe(get_all, str)
1 ответ
Вопрос к аннотации типа Python для пользовательского типа утки похож на этот вопрос и включает ссылку на протоколы (структурный подтип). Эта проблема создана PEP 544, которая имеет реализацию вtyping_extensions
,
Это означает, что для исправления вышеизложенного мы можем изменить GetableEndpoint
быть Protocol
,
from typing import TypeVar, List
from typing_extensions import Protocol
import abc
T = TypeVar('T')
class GetableEndpoint(Protocol[T]):
@abc.abstractmethod
def get(self, index=0, count=5) -> List[T]:
pass
@abc.abstractmethod
def count(self) -> int:
pass
Это позволяет использовать в PyCharm и Mypy полностью типизированное использование:
class A(Base):
def get(self, index=0, count=5) -> List[str]:
return _mock_a[index:index+count]
def count(self) -> int:
return len(_mock_a)
def get_all(base: GetableEndpoint[T]) -> List[T]:
step = 5
return [
item
for start in range(0, base.count(), step)
for item in base.get(start, step)
]
a = get_all(A())
print(a)
Я не могу получить pipe
работать, однако теперь у этого есть полностью рабочие типы, которые я считаю более важными.