An object implement __next__
returns successive stream items on each call.
When no more data are available, it raises a
StopIteration exception.
items.__next__()
is equivalent to using the built-in
next(items).
An object implementing __next__
is neither an Iterable,
nor an Iterator.
Iterables represent the stream of data.
They implement provisioning an Iterator,
via __iter__.
They do not have to implement __next__,
thus may not be Iterator themselves.
isinstance(obj, Iterable)
checks if the object
either explicitly implements __iter__(),
or inherits from collections.abc.Iterable.
The following class defines an Iterable type,
note that instances are "Iterables",
not the type itself.
Iterators implement both
__next__ and __iter__.
From the latter, all Iterators are Iterables,
while the converse is not true.
Iterators implement the traversal of a container,
giving access to data elements.
However, Iterators do not "perform iteration".
Loops cannot iterate over instances from a
class missing __iter__, save
for the legacy __getitem__ exception.
If the logic is contained in __next__,
then a type can be made into an iterator by
the sufficient def __iter__(self): return self.
iter() is a built-in that can
be used to wrap any callable into an Iterator.
For this, it requires a second arg: a sentinel value.
The output of iter(callable, sentinel) yields
from callable until the sentinel
is encountered, which stops the iteration.
The star-Iterable syntax deconstructs items,
for example, to fit into a variadic signature.
print takes *args that are printed sequentially,
thus star-Iterables allow printing all items
without using a loop.
Another good use is to map lists to their
first elem. or empty list.
print(*HasNextAndIter()) # 1 2 3
print(*HasNextAndIter().__iter__()) # 1 2 3
list_empty, list_full = [], ["f", "u", "l", "l"]
print("First from empty:", *list_empty[:1])
# new line, no error
print("First from full:", *list_full[:1])
# "f" and new line
for-loops extract a target from a
starred_list expression.
The expression is evaluated once, and can be
any that returns an Iterable object.
This Iterable
object is used to create an
Iterator.
The first item from the
Iterator is assigned to target.
# works since instances are Iterable
for target in HasNextAndIter():
print(target, end=', ')
print()
Legacy __getitem__
__getitem__()
iteration is a legacy fallback for iterable objects
not implementing __iter__().
It fails isinstance(_, Iterable),
but valid star-iterable/in loops
Provides tools for iteration.
A useful one is groupby,
which can be combined with sorted to make
a pseudo-SQL operation, similar to
the piping of sort | uniq.
from itertools import groupby
def grouped(items, *, by=lambda *_:_, desc=False):
_s = sorted(items, key=by, reverse=(not desc))
for k, g in groupby(_s, by): yield k, [*g]
Counting letter occurence
for c, group in grouped('aaabbaabbccccaacbc'):
print(c, "has", len(group), "occurences.")
Word count per word length
for w, group in grouped('this text by word length.'.split(), by=len):
print(w, group)
Html page count per public subdir
from pathlib import Path
for p, pages in grouped(Path().rglob('*.html'),
by=lambda _: _.parent):
# hidden / ignored files
if any(_[0] in '._' for _ in p.parts):
continue
print(" ", p, "has", len(pages), "pages.")