Reputation: 22175
I'd like to write the following generic Python code:
from itertools import chain
from typing import Sequence, Hashable, List, Tuple, Type, TypeVar
SequenceT = TypeVar('SequenceT', bound=Sequence)
HashableT = TypeVar('HashableT', bound=Hashable)
def merge_and_sort_into(seq_type, *iterables):
# type: (Type[SequenceT], *Iterable[HashableT]) -> SequenceT[HashableT]
return seq_type(sorted(set(chain(*iterables))))
def merge_and_sort_into_list(*iterables):
# type: (*Iterable[HashableT]) -> List[HashableT]
return merge_and_sort_into(list, *iterables)
def merge_and_sort_into_tuple(*iterables):
# type: (*Iterable[HashableT]) -> Tuple[HashableT]
return merge_and_sort_into(tuple, *iterables)
The code is fine, but mypy doesn't like the return type of merge_and_sort_into()
saying error: Type variable "SequenceT" used with arguments
. How can I pass a Sequence type into a function and also use that type as the return type? (note: I'm not capturing the fact that the value type for the sequence also needs to be comparable/sortable, but let's just ignore that).
Here is a version that mypy will accept, but it doesn't capture the constraint that type passed into merge_and_sort_into()
is its return type, hence the casts.
def merge_and_sort_into(seq_type, *iterables):
# type: (Callable[[Iterable[HashableT]], Sequence[HashableT]], *Iterable[HashableT]) -> Sequence[HashableT]
return seq_type(sorted(set(chain(*iterables))))
def merge_and_sort_into_list(*iterables):
# type: (*Iterable[HashableT]) -> List[HashableT]
return cast(List[HashableT], merge_and_sort_into(list, *iterables))
def merge_and_sort_into_tuple(*iterables):
# type: (*Iterable[HashableT]) -> Tuple[HashableT]
return cast(Tuple[HashableT], merge_and_sort_into(tuple, *iterables))
Upvotes: 2
Views: 1455
Reputation: 4253
Mypy does not support generic type variables because they would require the type system to support higher kinds, as discussed in this comment.
Even if Mypy supported generic type variables, the type signature of the original function would be incorrect, because not all Sequence
s can be constructed from an iterable. For example,
class EmptyList(Sequence[T]):
def __init__(self): pass
def __getitem__(self, item): raise IndexError
def __len__(self): return 0
EmptyList([1, 2, 3]) # TypeError
The most straightforward solution for your specific case is probably to simply allow non-sequence return types, and use Callable
instead of Type
.
T, R = TypeVar('T'), TypeVar('R')
def chain_into(into: Callable[[Iterable[T]], R], *iters: Iterable[T]) -> R:
return into(chain.from_iterable(iters))
Upvotes: 2