Я пытаюсь реализовать генератор, который будет возвращать пару элемента последовательности и логическое значение, указывающее, является ли этот элемент последним.
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.
Причина в том, что вы используете chain
. Простой вызов reveal_type(chain(sequence, sequence))
покажет, что chain[_T1'-1]
, поскольку они одного типа, но reveal_type(chain(sequence, [object()]))
внутри annotate_last(...)
покажет вам, что объединение sequence
с [object()]
является фактической причиной, поскольку оно приводит к chain[builtins.object]
, в отличие от утверждения в заголовке этого вопроса: " Ввод с помощью TypeVar преобразует тип в объект».
@Aemyl Спасибо за комментарий. Ваше предложение не сработает, поскольку T1_
будет несвязанным в глобальной области видимости. И внутри функции будет ошибка Incompatible types in assignment
Определение pairwise
гласит, что он принимает итерацию _T1
и возвращает итерацию кортежей одного и того же типа, но вы предоставляете ему итерацию _T1
и object
, поэтому правильно ожидать, что он вернет итерацию кортежей этого объединения. - хотя мы видим, что на самом деле это никогда не произойдет во время выполнения. Однако, если вы делаете это только для того, чтобы получить работающий annotated_last
, я думаю, есть более простые решения. Ваш вопрос касается только ошибки или вам нужно только решить ее, чтобы получить правильный и эффективный annotated_last
?
@Vladimir Владимир Я не ожидал, что мое предложение решит вашу проблему, скорее я надеялся, что оно сможет подтвердить, что корень ошибки mypy заключается в том, что _MISSING
не считается типом _T1
. Однако вы абсолютно правы в том, что _T1
в глобальной области видимости — это не то же самое, что _T1
в аннотированных функциях, я это упустил из виду.
@Grismar Да, я хотел бы исправить эту ошибку. Есть ли у вас какие-либо предложения о том, как это сделать?
Это потому, что вы создали итератор 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
, чтобы сразу было ясно, что он предназначен для выполнения только после успешной итерации через полная последовательность - но это строка длиннее и функционально идентична, так что это вопрос стиля.
Я думаю, что
_MISSING
не относится к типу_T1
, поэтому возвратchain(sequence, [_MISSING])
равенIterable[Union[T1, object]]
. Что произойдет, если вы пометите_MISSING
как_MISSING: _T1 = object()
?