Ввод с помощью TypeVar преобразует тип в объект

Я пытаюсь реализовать генератор, который будет возвращать пару элемента последовательности и логическое значение, указывающее, является ли этот элемент последним.

from collections.abc import Generator, Iterable
from itertools import chain, tee
from typing import TypeVar

_T1 = TypeVar('_T1')
_MISSING = object()


def pairwise(iterable: Iterable[_T1]) -> Iterable[tuple[_T1, _T1]]:
    # See https://docs.python.org/3.9/library/itertools.html#itertools-recipes
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)


def annotated_last(sequence: Iterable[_T1]) -> Generator[tuple[_T1, bool], Any, None]:
    for current_item, next_item in pairwise(chain(sequence, [_MISSING])):
        is_last = next_item is _MISSING
        yield current_item, is_last  # <-- mypy error

Однако mypy возвращает эту ошибку:

Incompatible types in "yield" (actual type "tuple[object, bool]", expected type "tuple[_T1, bool]")

Подскажите, как правильно аннотировать типы в этих функциях.

Я использую Python версии 3.9.19.

Я думаю, что _MISSING не относится к типу _T1, поэтому возврат chain(sequence, [_MISSING]) равен Iterable[Union[T1, object]]. Что произойдет, если вы пометите _MISSING как _MISSING: _T1 = object()?

Aemyl 20.06.2024 07:47

Причина в том, что вы используете chain. Простой вызов reveal_type(chain(sequence, sequence)) покажет, что chain[_T1'-1], поскольку они одного типа, но reveal_type(chain(sequence, [object()])) внутри annotate_last(...) покажет вам, что объединение sequence с [object()] является фактической причиной, поскольку оно приводит к chain[builtins.object], в отличие от утверждения в заголовке этого вопроса: " Ввод с помощью TypeVar преобразует тип в объект».

metatoaster 20.06.2024 07:53

@Aemyl Спасибо за комментарий. Ваше предложение не сработает, поскольку T1_ будет несвязанным в глобальной области видимости. И внутри функции будет ошибка Incompatible types in assignment

Vladimir 20.06.2024 08:04

Определение pairwise гласит, что он принимает итерацию _T1 и возвращает итерацию кортежей одного и того же типа, но вы предоставляете ему итерацию _T1 и object, поэтому правильно ожидать, что он вернет итерацию кортежей этого объединения. - хотя мы видим, что на самом деле это никогда не произойдет во время выполнения. Однако, если вы делаете это только для того, чтобы получить работающий annotated_last, я думаю, есть более простые решения. Ваш вопрос касается только ошибки или вам нужно только решить ее, чтобы получить правильный и эффективный annotated_last?

Grismar 20.06.2024 08:05

@Vladimir Владимир Я не ожидал, что мое предложение решит вашу проблему, скорее я надеялся, что оно сможет подтвердить, что корень ошибки mypy заключается в том, что _MISSING не считается типом _T1. Однако вы абсолютно правы в том, что _T1 в глобальной области видимости — это не то же самое, что _T1 в аннотированных функциях, я это упустил из виду.

Aemyl 20.06.2024 08:09

@Grismar Да, я хотел бы исправить эту ошибку. Есть ли у вас какие-либо предложения о том, как это сделать?

Vladimir 20.06.2024 08:13
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
6
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это потому, что вы создали итератор chain следующим образом: chain(sequence, [_MISSING]), и вывод типа должен вывести наиболее общий тип из этих аргументов, но _MISSING является объектом, поэтому он должен быть итератором object. Обратите внимание: вы можете реализовать нужную функцию с нужной сигнатурой напрямую (хотя и менее элегантно), делая что-то вроде:

from collections.abc import Iterator, Iterable
from typing import TypeVar

_T1 = TypeVar('_T1')


def annotated_last(sequence: Iterable[_T1]) -> Iterator[tuple[_T1, bool]]:
    it = iter(sequence)
    try:
        previous = next(it)
    except StopIteration:
        return
    for current in it:
        yield previous, False
        previous = current
    yield previous, True

Я собирался отправить ответ с тем же решением, хотя я вложил for в раздел try и имел окончательный результат в else для for, чтобы сразу было ясно, что он предназначен для выполнения только после успешной итерации через полная последовательность - но это строка длиннее и функционально идентична, так что это вопрос стиля.

Grismar 20.06.2024 08:40

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