Какой аннотацией можно указать индексируемую последовательность строк, которая не является строкой?

Как аннотировать тип параметра, который должен быть индексируемой последовательностью строк, но при этом не быть строкой?

Под индексируемой последовательностью я понимаю линейно упорядоченную структуру данных, из которой можно извлечь содержимое по его порядковому индексу (список, кортеж, матрица, массив, вектор, тензор). Строка, т.е. объект типа str, является индексируемой последовательностью, содержащей в качестве элементов отдельные символы. Но понятия символа как самостоятельного класса в Python нет, каждый символ - это объект типа str. То есть, строка может рассматриваться как индексируемая последовательность строк. И в этом заключается трудность, когда на уровне статической типизации нужно поймать ошибки вида func("abc") там, где должно быть, например, func(["abc"]).

Сейчас я использую для аннотации таких параметров Sequence[str] и MutableSequence[str]. Первый нежелателен, потому что допускает использование строки в качестве последовательности строк. Второй кроме строк исключает, например, кортежи, что тоже не хорошо. Хотелось бы решить вопрос на уровне статической типизации, чтобы не перегружать исполняемый код дополнительной проверкой типа, которая не имеет отношения к выполняемому алгоритму.

# experiment.py

from collections.abc import Sequence, MutableSequence


def foo(seq: Sequence[str]) -> None:
    pass

def bar(seq: MutableSequence[str]) -> None:
    pass

foo(['a', 'b', 'c'])   # Acceptable
foo(('a', 'b', 'c'))   # Acceptable
foo('abc')             # Not acceptable !
bar(['a', 'b', 'c'])   # Acceptable
bar(('a', 'b', 'c'))   # Should be acceptable !
bar('abc')             # Not acceptable
$ mypy experiment.py
experiment.py:16: error: Argument 1 to "bar" has incompatible type "tuple[str, str, str]"; expected "MutableSequence[str]"  [arg-type]
experiment.py:17: error: Argument 1 to "bar" has incompatible type "str"; expected "MutableSequence[str]"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)

P.S. Ответы по аналогичному вопросу на англоязычном сайте.


Ответы (2 шт):

Автор решения: Рустам Рысаев

Использовать Sequence[str], но явно исключатьstr, используя TypeGuard. Может подойдет:

def is_not_str_sequence(seq: Sequence[str]) -> TypeGuard[Sequence[str]]:
    return not isinstance(seq, str)

def foo(seq: Sequence[str]) -> None:
    assert is_not_str_sequence(seq), "Argument must not be a string"
    print(seq)

foo(['a', 'b', 'c'])   
foo(('a', 'b', 'c'))   
foo('abc')             
→ Ссылка
Автор решения: Amgarak

Могу предложить посмотреть в сторону объединения типов:

from typing import Union, List, Tuple

SequenceString = Union[List[str], Tuple[str, ...]]
# SequenceString = List[str] | Tuple[str, ...]

def foo(seq: SequenceString) -> None:
    pass

foo(["a", "b", "c"])  # Acceptable
foo(("a", "b", "c"))  # Acceptable
foo("abc")  # Not acceptable

Ошибка от mypy:

Argument 1 to "foo" has incompatible type "str"; expected "list[str] | tuple[str, ...]"
→ Ссылка