Sorry only just catching these messages.
I’ve tried to cover the background issue in this post, see the next one for possible solutions.
If we desire to create our own custom iterator class, we must implement the iterator protocol, meaning we need to have a class that defines at minimum the __iter__() and __next__() methods.
The __iter__() method must always return the iterator object itself. Typically, this is accomplished by returning self. It can also include some class member initializing.
The __next()__ method must either return the next value available or raise the StopIteration
exception. It can also include any number of operations.
This does describe an iterator but the problem I had with this lesson is it tries to (or at least used to) squeeze the iterator behaviour into the ClassroomOrganizer
class which is really a container type, see e.g. https://docs.python.org/3/reference/datamodel.html?emulating-container-types#emulating-container-types. They should really be kept separate or you’ll probably never meet Python’s protocol for iterators.
So the closest match to the lessons in my eyes would be to create two classes, one ClassroomOrganizer
that stores and orders the data and a second class, an iterator template than can access the data within a ClassroomOrganizer
instance.
Bear in mind this is exactly how Python currently treats normal collections/containers (ignoring any precise definition here). Container types like list
or perhaps a very similar tool like collections.OrderedDict
(a standard module class that acts as a container which can order it’s contents) are iterable. They are not themselves iterators, that task is handled by other types.
To see these actual different iterator types in practice…
from collections import OrderedDict
foo = iter(list())
bar = iter(OrderedDict())
# Note the types here are not list or OrderedDict but distinct iterators
print(f"{type(foo)!r}") # <class 'list_iterator'>
print(f"{type(bar)!r}") # <class 'odict_iterator'>
This is really an implementation detail but it matters because the behaviour of iterables and iterators is quite different. For a start iterables have no __next__
method (that’s reserved for actual iterators) and calling next()
on an iterable will simply throw exceptions e.g.
x = [1, 2, 3,]
next(x) # Exception thrown
In addition to the behaviour of next()
iterable container types behave differently to iterators when you call iter()
on them. You can get typically get several different independent iterators from a single iterable (maybe a couple of CPython things violate this but it’s rare)-
# Standard containers return new iterators on each call-
x = [1, 2, 3,]
y = iter(x)
z = iter(x)
print(y is z) # False
print(next(y), next(y)) # 2
# The z iterator instance is independent of y
print(next(z)) # 1
On the other hand iterators should return themselves…
x = [1, 2, 3,]
y = iter(x)
iter_y = iter(y) # called on an iterator!
print(next(y)) # 1
# It's the SAME ITERATOR!
print(next(iter_y)) # 2
In general if you’re defining a new container/collection then making it iterable is wise, making it an iterator probably isn’t (I won’t swear to every possible situation but following this pattern for custom container types is normally a good idea).
Some potentially useful reading-
https://docs.python.org/3/reference/datamodel.html?emulating-container-types#emulating-container-types
https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes
https://docs.python.org/3/library/stdtypes.html#typeiter
https://docs.python.org/3/glossary.html#term-iterator
https://docs.python.org/3/glossary.html#term-iterable
Iterator - Wikipedia
Iterator pattern - Wikipedia
Collection (abstract data type) - Wikipedia
Container (abstract data type) - Wikipedia
Long story short, for the requirements of this lesson (define a custom iterator) I suggest using at least two classes (an iterable and an iterator).