Соединение опциональности двух типов

Пример кода

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-play.net/…. Разве вы не пытаетесь повторно использовать предыдущий O, определенный где-то ранее? Never - это именно тот "StrawManType" - Никогда не является необитаемым типом, поэтому x | Never == x по определению.

STerliakov 13.07.2024 04:10

Интересный. Моя вина, мне следовало протестировать это на игровой площадке 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 не справляется с этим хорошо.

Beaudry Chase 13.07.2024 04:28

Ограничение можно вообще снять. Немного хуже с точки зрения ясности и безопасности, но за поддержку нескольких типов проверки приходится что-то платить. Детская площадка

STerliakov 16.07.2024 14:47

(и подумайте об открытии билета на пирайт, если его еще нет — Эрику, должно быть, надоели мои идеи, но это действительно похоже на ошибку)

STerliakov 16.07.2024 14:48

Я открыл заявку на проект Pyright. Вы можете следить за этой веткой здесь: github.com/microsoft/pyright/issues/8432

Beaudry Chase 16.07.2024 15:52
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
6
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
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

Другие вопросы по теме