Typehints for Sized Iterable in Python

PythonPython 3.xType Hinting

Python Problem Overview


I have a function that uses the len function on one of it's parameters and iterates over the parameter. Now I can choose whether to annotate the type with Iterable or with Sized, but both gives errors in mypy.

from typing import Sized, Iterable


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

Gives

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

While

def foo(some_thing: Sized):
...

Gives

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

Since there is no Intersection as discussed in this issue I need to have some kind of mixed class.

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'])

This gives an error when using foo with a list.

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

This is not too surprising since:

>>> SizedIterable.__subclasscheck__(list)
False

So I defined a __subclasshook__ (see docs).

class SizedIterable(Sized, Iterable[str], metaclass=ABCMeta):
    
    @classmethod
    def __subclasshook__(cls, subclass):
        return Sized.__subclasscheck__(subclass) and Iterable.__subclasscheck__(subclass)

Then the subclass check works:

>>> SizedIterable.__subclasscheck__(list)
True

But mypy still complains about my list.

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

How can I use type hints when using both the len function and iterate over my parameter? I think casting foo(cast(SizedIterable, ['a', 'b', 'c'])) is not a good solution.

Python Solutions


Solution 1 - Python

Starting from Python3.6 there's a new type called Collection. See here.

Solution 2 - Python

In the future Protocols will be introduced. They are already available through typing_extensions. See also PEP 544. Using Protocol the code above would be:

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 takes that code without complaining. But PyCharm is saying

> Expected type 'SizedIterable', got 'List[str]'

about the last line.

Solution 3 - Python

You should go with Sequence from typing if you plan to use only list or tuple and access its elements by index, like x[0]. Sequence is both Sized and Iterable, see here.

Solution 4 - Python

Maybe you need a class like this?

class MyArray:
  _array: list

  def as_tuple(self):
    return tuple(self._array)

  def as_list(self):
    return self._array
    
    # More Feature to implement

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBenjaminView Question on Stackoverflow
Solution 1 - PythonAvisionView Answer on Stackoverflow
Solution 2 - PythonBenjaminView Answer on Stackoverflow
Solution 3 - PythonhansView Answer on Stackoverflow
Solution 4 - PythonSinri EdogawaView Answer on Stackoverflow