There is no general way to solve this: Sequence includes types which cannot be concatenated in a generic way. For example, there is no way to concatenate arbitrary range objects to create a new range and keep all elements.
One must decide on a concrete means of concatenation, and restrict the accepted types to those providing the required operations.
The simplest approach is for the function to only request the operations needed. In case the pre-built protocols in typing are not sufficient, one can fall back to define a custom typing.Protocol for the requested operations.
Since concat1/concat_add requires the + implementation, a Protocol with __add__ is needed. Also, since addition usually works on similar types, __add__ must be parameterized over the concrete type – otherwise, the Protocol asks for all addable types that can be added to all other addable types.
# TypeVar to parameterize for specific types
SA = TypeVar('SA', bound='SupportsAdd')
class SupportsAdd(Protocol):
"""Any type T where +(:T, :T) -> T"""
def __add__(self: SA, other: SA) -> SA: ...
def concat_add(a: SA, b: SA) -> SA:
return a + b
This is sufficient to type-safely concatenate the basic sequences, and reject mixed-type concatenation.
reveal_type(concat_add([1, 2, 3], [12, 17])) # note: Revealed type is 'builtins.list*[builtins.int]'
reveal_type(concat_add("abc", "xyz")) # note: Revealed type is 'builtins.str*'
reveal_type(concat_add([1, 2, 3], "xyz")) # error: ...
Be aware that this allows concatenating any type that implements __add__, for example int. If further restrictions are desired, define the Protocol more closely – for example by requiring __len__ and __getitem__.
Typing concatenation via chaining is a bit more complex, but follows the same approach: A Protocol defines the capabilities needed by the function, but in order to be type-safe the elements should be typed as well.
# TypeVar to parameterize for specific types and element types
C = TypeVar('C', bound='Chainable')
T = TypeVar('T', covariant=True)
# Parameterized by the element type T
class Chainable(Protocol[T]):
"""Any type C[T] where C[T](:Iterable[T]) -> C[T] and iter(:C[T]) -> Iterable[T]"""
def __init__(self, items: Iterable[T]): ...
def __iter__(self) -> Iterator[T]: ...
def concat_chain(a: C, b: C) -> C:
T = type(a)
return T(chain(a, b))
This is sufficient to type-safely concatenate sequences constructed from themselves, and reject mixed-type concatenation and non-sequences.
reveal_type(concat_chain([1, 2, 3], [12, 17])) # note: Revealed type is 'builtins.list*[builtins.int]'
reveal_type(concat_chain("abc", "xyz")) # note: Revealed type is 'builtins.str*'
reveal_type(concat_chain([1, 2, 3], "xyz")) # error: ...
reveal_type(concat_chain(1, 2)) # error: ...
T = TypeVar('T', bound=Sequence); def concat(a: T, b: T) -> T: .... However, the issue is that just because a type is a sequence doesn't guarantee that it has a constructor which takes an iterable as an argument. It's true forlist,tuple, etc, but does not need to be true in general.rangeis a sequence type but cannot be meaningfully concatenated to create a newrange– all but the most basic cases break the invariants ofrange. As long as your goal is "concatenate arbitrarySequencetypes", different approaches will just create different errors. MyPy is unhappy because what you are doing is wrong, and it is MyPy's job to tell you that.concat1. According to the type hints, the callconcat1('abc', ['d', 'e', 'f'])is valid, but Python cannot concatenate sequences of different types. Unfortunately, typing in Python is becoming more problematic than helpful in a number of practical cases.