Типичные значения для переменной Iterable в Python

У меня есть функция, которая использует функцию len для одного из параметров и выполняет итерацию по параметру. Теперь я могу выбрать, нужно ли аннотировать тип с помощью Iterable или Sized, но оба дают ошибки в mypy.

from typing import Sized, Iterable


def foo(some_thing: Iterable):
    print(len(some_thing))
    for part in some_thing:
        print(part)

дает

error: Argument 1 to "len" has incompatible type "Iterable[Any]"; expected "Sized"

В то время как

def foo(some_thing: Sized):
...

дает

error: Iterable expected
error: "Sized" has no attribute "__iter__"

Поскольку нет Intersection как обсуждалось в этом выпуске, мне нужен какой-то смешанный класс.

from abc import ABCMeta
from typing import Sized, Iterable


class SizedIterable(Sized, Iterable[str], metaclass=ABCMeta):
    pass


def foo(some_thing: SizedIterable):
    print(len(some_thing))
    for part in some_thing:
        print(part)


foo(['a', 'b', 'c'])

Это дает ошибку при использовании foo со list.

error: Argument 1 to "foo" has incompatible type "List[str]"; expected "SizedIterable"

Это не слишком удивительно, так как:

>>> SizedIterable.__subclasscheck__(list)
False

Поэтому я определил __subclasshook__ (см. Документы).

class SizedIterable(Sized, Iterable[str], metaclass=ABCMeta):

    @classmethod
    def __subclasshook__(cls, subclass):
        return Sized.__subclasscheck__(subclass) and Iterable.__subclasscheck__(subclass)

Тогда проверка подкласса работает:

>>> SizedIterable.__subclasscheck__(list)
True

Но mypy все еще жалуется на мой list.

error: Argument 1 to "foo" has incompatible type "List[str]"; expected "SizedIterable"

Как я могу использовать подсказки типов при использовании как функции len и перебора моего параметра? Я думаю, что приведение типа foo(cast(SizedIterable, ['a', 'b', 'c'])) не является хорошим решением.

Ответ 1

Начиная с Python3.6 появился новый тип под названием Collection. Смотрите здесь

Ответ 2

В будущем будет введен Protocol. Они уже доступны через typing_extensions. См. Также PEP 544. Используя Protocol приведенный выше код:

from typing_extensions import Protocol


class SizedIterable(Protocol):

    def __len__(self):
        pass

    def __iter__(self):
        pass


def foo(some_thing: SizedIterable):
    print(len(some_thing))
    for part in some_thing:
        print(part)


foo(['a', 'b', 'c'])

mypy берет этот код без жалоб. Но PyCharm говорит

Ожидаемый тип "SizedIterable", получил "List [str]"

о последней строке.

Ответ 3

Вам следует идти с Sequence из печатая, если вы планируете использовать только список или кортеж и получить доступ к его элементам по индексу, как x[0].