I = TypeVar("I", bound=Optional[Iterable])
O = TypeVar("O", bound=Optional[Mappable])
class Worker(Generic[I, O]):
@abstractmethod
def do_work(self, input: I) -> O:
pass
worker = Worker[list, dict]()
worker_with_optional = Worker[Optional[list], Optional[dict]]()
worker_bad_types = Worker[Optional[list], dict]()
Реальный код в примере может показаться немного надуманным, но это был лучший способ абстрактно представить мою проблему, который я мог придумать. Что я изо всех сил пытаюсь сделать, так это настроить TypeVars
для ввода в Generic
, который позволил бы пользователям создавать worker
и worker_with_optional
, но не worker_bad_types
.
Идея, которую я пытаюсь воплотить в коде, — это взаимосвязь двух типов ввода. Я хочу потребовать, чтобы I
и O
были необязательными или они оба должны иметь значение.
Лучшим обходным решением для желаемой функциональности было бы создание двух таких версий класса:
I = TypeVar("I", bound=Iterable)
O = TypeVar("O", bound=Mappable)
class Worker(Generic[I, O]):
@abstractmethod
def do_work(self, input: I) -> O:
pass
class WorkerWithOptional(Generic[I, O]):
@abstractmethod
def do_work(self, input: Optional[I]) -> Optional[O]:
pass
worker = Worker[list, dict]()
worker_with_optional = WorkerWithOptional[list, dict]()
# worker_bad_types now has a type error because None is not Iterable
worker_bad_types = Worker[Optional[int], str]()
Этот подход обеспечивает желаемые ограничения типов, но я бы не хотел делать две копии своего класса, чтобы учитывать это. Есть ли способ связать опциональность типов, передаваемых в Generic
, чтобы они всегда совпадали?
Интересный. Моя вина, мне следовало протестировать это на игровой площадке mypy. Ты прав. mypy правильно обрабатывает код ответа. Я протестировал его в своей локальной среде, и на самом деле именно pylance (версия 2024.7.1) выдавала мне ошибку: Type "Never" cannot be assigned to type variable "O@TestClass" Type "Never" is incompatible with constrained type variable "O@TestClass"PylancereportInvalidTypeArguments
. Немного расстраивает то, что pylance не справляется с этим хорошо.
Ограничение можно вообще снять. Немного хуже с точки зрения ясности и безопасности, но за поддержку нескольких типов проверки приходится что-то платить. Детская площадка
(и подумайте об открытии билета на пирайт, если его еще нет — Эрику, должно быть, надоели мои идеи, но это действительно похоже на ошибку)
Я открыл заявку на проект Pyright. Вы можете следить за этой веткой здесь: github.com/microsoft/pyright/issues/8432
I = TypeVar("I", bound=Iterable)
O = TypeVar("O", bound=Mapping)
T = TypeVar("T", None, Never)
class BaseWorker(Generic[I, O, T]):
@abstractmethod
def do_work(self, input: I | T) -> O | T:
pass
Worker: TypeAlias = BaseWorker[I, O, Never]
WorkerWithOptional: TypeAlias = BaseWorker[I, O, None]
Никогда не является непреодолимым, то есть I | Never
упрощается до I
. Мы воспользовались этим, чтобы позволить управлять вашей необязательностью с помощью параметра T
.
Если вы запустите игровую площадку mypy из приведенного ниже кода, вы увидите, что из 4 тестовых примеров только #2 получает ошибку типа, как и ожидалось.
class TestWorker(Worker[list[int], dict[int, int]]):
def do_work(self, input: list[int]) -> dict[int, int]:
return {x: 1 for x in input}
class TestWorkerWithOptional(WorkerWithOptional[list[int], dict[int, int]]):
def do_work(self, input: list[int] | None) -> dict[int, int] | None:
return None if input is None else {x: 1 for x in input}
worker = TestWorker()
worker_w_opt = TestWorkerWithOptional()
worker.do_work([1]) # 1
worker.do_work(None) # 2
worker_w_opt.do_work([1]) # 3
worker_w_opt.do_work(None) # 4
Я не могу воспроизвести вашу проблему в редактировании: mypy-play.net/…. Разве вы не пытаетесь повторно использовать предыдущий
O
, определенный где-то ранее?Never
- это именно тот "StrawManType" - Никогда не является необитаемым типом, поэтомуx | Never == x
по определению.