(Я новичок в аннотациях типов Python и mypy, поэтому подробно описываю свою проблему, чтобы не столкнуться с проблемой XY)
У меня есть два абстрактных класса, которые обмениваются значениями произвольного, но фиксированного типа:
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Generic, TypeVar
T = TypeVar('T') # result type
class Command(ABC, Generic[T]):
@abstractmethod
def execute(self, runner: Runner[T]) -> T:
raise NotImplementedError()
class Runner(ABC, Generic[T]):
def run(self, command: Command[T]) -> T:
return command.execute(self)
В моей реализации этого интерфейса подкласс Command
должен получить доступ к атрибуту моего подкласса Runner
(представьте, что команда может адаптироваться к бегунам с разными возможностями):
class MyCommand(Command[bool]):
def execute(self, runner: Runner[bool]) -> bool:
# Pseudo code to illustrate dependency on runner's attributes
return runner.magic_level > 10
class MyRunner(Runner[bool]):
magic_level: int = 20
Это работает, как ожидалось, но не удовлетворяет mypy:
mypy_sandbox.py:24: error: "Runner[bool]" has no attribute "magic_level" [attr-defined]
Очевидно, mypy верен: атрибут magic_level
определен в MyRunner
, но не в Runner
(который является типом аргумента для execute
). Так что интерфейс слишком общий - команда не должна работать с каким-либо бегуном, только с некоторыми бегунами. Итак, давайте сделаем Command
универсальным для переменной второго типа, чтобы захватить поддерживаемый класс бегуна:
R = TypeVar('R') # runner type
T = TypeVar('T') # result type
class Command(ABC, Generic[T, R]):
@abstractmethod
def execute(self, runner: R) -> T:
raise NotImplementedError()
class Runner(ABC, Generic[T]):
def run(self, command: Command[T, Runner[T]]) -> T:
return command.execute(self)
class MyCommand(Command[bool, MyRunner]):
def execute(self, runner: MyRunner) -> bool:
# Pseudo code to illustrate dependency on runner's attributes
return runner.magic_level > 10
# MyRunner defined as before
Это удовлетворяет mypy, но когда я пытаюсь использовать код, mypy снова жалуется:
if __name__ == '__main__':
command = MyCommand()
runner = MyRunner()
print(runner.run(command))
mypy_sandbox.py:35: error: Argument 1 to "run" of "Runner" has incompatible type "MyCommand"; expected "Command[bool, Runner[bool]]" [arg-type]
На этот раз я даже не понимаю ошибку: MyCommand
является подклассом Command[bool, MyRunner]
, а MyRunner
является подклассом Runner[bool]
, так почему MyCommand
несовместим с Command[bool, Runner[bool]]
?
И если mypy был удовлетворен, я, вероятно, мог бы реализовать подкласс Command
с подклассом Runner
, который использует другое значение для T
(поскольку R
не привязан к T
) без жалоб mypy. Я пробовал R = TypeVar('R', bound='Runner[T]')
, но это вызывает еще одну ошибку:
error: Type variable "mypy_sandbox.T" is unbound [valid-type]
Как я могу ввести аннотации для этого, чтобы расширения, описанные выше, были возможны, но при этом проверялись правильные типы?
class Command<T, R extends Runner<T>>
, но я не думаю, что аннотации типов Python поддерживают переменные типа с общими границами. - person user2357112 supports Monica   schedule 18.11.2020