__iter__ method returns a non-iterator¶
ID: py/iter-returns-non-iterator Kind: problem Security severity: Severity: error Precision: high Tags: - quality - reliability - correctness Query suites: - python-code-quality.qls - python-security-and-quality.qls Click to see the query in the CodeQL repository
The __iter__ method of a class should always return an iterator.
Iterators must implement both __next__ and __iter__ for Python 3, or both next and __iter__ for Python 2. The __iter__ method of the iterator must return the iterator object itself.
Iteration in Python relies on this behavior and attempting to iterate over an instance of a class with an incorrect __iter__ method can raise a TypeError.
Recommendation¶
Make sure the value returned by __iter__ implements the full iterator protocol.
Example¶
In this example, we have implemented our own version of range, extending the normal functionality with the ability to skip some elements by using the skip method. However, the iterator MyRangeIterator does not fully implement the iterator protocol (namely it is missing __iter__).
Iterating over the elements in the range seems to work on the surface, for example the code x = sum(my_range) gives the expected result. However, if we run sum(iter(my_range)) we get a TypeError: 'MyRangeIterator' object is not iterable.
If we try to skip some elements using our custom method, for example y = sum(my_range.skip({6,9})), this also raises a TypeError.
The fix is to implement the __iter__ method in MyRangeIterator.
class MyRange(object): def __init__(self, low, high): self.low = low self.high = high def __iter__(self): return MyRangeIterator(self.low, self.high) def skip(self, to_skip): return MyRangeIterator(self.low, self.high, to_skip) class MyRangeIterator(object): def __init__(self, low, high, skip=None): self.current = low self.high = high self.skip = skip def __next__(self): if self.current >= self.high: raise StopIteration to_return = self.current self.current += 1 if self.skip and to_return in self.skip: return self.__next__() return to_return # Problem is fixed by uncommenting these lines # def __iter__(self): # return self my_range = MyRange(0,10) x = sum(my_range) # x = 45 y = sum(my_range.skip({6,9})) # TypeError: 'MyRangeIterator' object is not iterable References¶
Python Language Reference: object.iter.
Python Standard Library: Iterator Types.