'Why does mypy not consider a class as iterable if it has __len__ and __getitem__ but no __iter__
I was playing around with mypy and some basic iteration in Python and wrote the below code base:
from typing import Iterator
from datetime import date, timedelta
class DateIterator:
def __init__(self, start_date, end_date):
self.start_date = start_date
self.end_date = end_date
self._total_dates = self._get_all_dates()
def _get_all_dates(self) -> Iterator[date]:
current_day = self.start_date
while current_day <= self.end_date:
yield current_day
current_day += timedelta(days=1)
def __len__(self):
print("Calling the len function...")
return len(self._total_dates)
def __getitem__(self, index):
print(f"Calling getitem with value of index as {index}")
return self._total_dates[index]
if __name__ == "__main__":
date_iterator = DateIterator(date(2019, 1, 1), date(2019, 1, 15))
for new_date in date_iterator:
print(new_date)
date_str = ",".join([str(new_date) for new_date in date_iterator])
print(date_str)
print(f"Checking the length of the collection {len(date_iterator)}")
print(f"Checking if indexing works : {date_iterator[4]}")
Now to also play around with mypy i got the below issues:
iterator_test_with_getitem.py:30: error: Cannot assign to a type
iterator_test_with_getitem.py:30: error: "DateIterator" has no attribute "__iter__" (not iterable)
iterator_test_with_getitem.py:33: error: "DateIterator" has no attribute "__iter__" (not iterable)
Found 3 errors in 1 file (checked 1 source file)
Can someone please guide me that if an object is iterable with the addition of __len__ and __getitem__ methods then why is mypy complaining when it has no iter method
Also can someone please tell me what the issue with line 30 is . I don't find any logical explanation of that error as well.
Solution 1:[1]
Mypy -- and PEP 484 type checkers in general -- define an iterable to be any class that defines the __iter__ method. You can see the exact definition of the Iterable type in Typeshed, the collection of type hints for the standard library: https://github.com/python/typeshed/blob/master/stdlib/3/typing.pyi#L146
The reason why Iterable is defined in this way and excludes types that only define __getitem__ and __len__ is because:
The only way to say that a type must either implement
__iter__or implement__getitem__/__len__is to use a Union -- and defining Iterable to be a union complicates life a bit for anybody who wants to make extensive use of the Iterable type in their own code.Conversely, it's trivial for a class that defines
__getitem__and__len__to define their own__iter__method. For example, you could do something as simple as this:def __iter__(self) -> Iterator[date]: for i in range(len(self)): yield self[i]Or alternatively, something like this (assuming you fixed your constructor so
self._total_datesis a list, not a generator):def __iter__(self) -> Iterator[date]: return iter(self._total_dates)
So, given this cost-benefit tradeoff, it makes sense to define Iterable to just be any object that implements __iter__. It's not much of a burden for people who are defining custom classes and simplifies life for people who want to write functions manipulating iterables.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Michael0x2a |
