Reputation: 5333
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?
Upvotes: 3
Views: 41
Reputation: 226486
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
Upvotes: 5
Reputation: 42746
Just implementing the __getItem__
method python will try to acces all indexes from 0
to infinite
, so if you dont specify when to stop it will keep calling the method.
Just check if the index value is higher than the size for example:
class Limited(object):
def __init__(self, size=5):
self.size = size
def __len__(self):
return self.size
def __getitem__(self, item):
if item < self.size:
return item*10
raise IndexError
Here you have a live example
Upvotes: 2