I lived with the assumption, that defining the _ _len_ _ and _ _getitem_ _ methods are sufficient for a class, so its instances can be iterated over via a for element in instance:, until it got violated an example. My original code is quite different, but this shows the problem nicely entering an endless loop:
class Limited(object):
def __init__(self, size=5):
self.size = size
def __len__(self):
return self.size
def __getitem__(self, item):
return item*10
if __name__ == "__main__":
test = Limited(4)
assert len(test) == 4
for q in test:
print q
I could not find a specific reference to the requirements for terminating an iteration loop, but it seems that an exception like an IndexError or StopIteration is necessary for termination, if one does not want to comply to full Iterator protocol.
Is that correct and where to find it documented?
Yes, an IndexError is required to terminate.
See the documentation for __getitem__() which has the note:
Note for loops expect that an IndexError will be raised for illegal indexes to allow proper detection of the end of the sequence.
The logic for creating an iterator is in Objects/iterobject.c:
static PyObject *
iter_iternext(PyObject *iterator)
{
seqiterobject *it;
PyObject *seq;
PyObject *result;
assert(PySeqIter_Check(iterator));
it = (seqiterobject *)iterator;
seq = it->it_seq;
if (seq == NULL)
return NULL;
if (it->it_index == PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_OverflowError,
"iter index too large");
return NULL;
}
result = PySequence_GetItem(seq, it->it_index);
if (result != NULL) {
it->it_index++;
return result;
}
if (PyErr_ExceptionMatches(PyExc_IndexError) ||
PyErr_ExceptionMatches(PyExc_StopIteration))
{
PyErr_Clear();
Py_DECREF(seq);
it->it_seq = NULL;
}
return NULL;
}
To fix the OP's code, only two lines need to be added to the beginning of the __getitem__() method:
class Limited(object):
def __init__(self, size=5):
self.size = size
def __len__(self):
return self.size
def __getitem__(self, item):
if item >= len(self):
raise IndexError
return item*10
if __name__ == "__main__":
test = Limited(4)
assert len(test) == 4
for q in test:
print(q)
This outputs a finite sequence:
0
10
20
30
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With