Show Python's iter() and next() working together on a list and on a generator.
The function iter() turns an iterable into an iterator, and the function next() pulls one value out of an iterator. Every for loop in Python is built from exactly these two operations.
it = iter([10, 20, 30])
print(type(it).__name__)
print(next(it))
print(next(it))
# Calling iter on something that is already an iterator returns it unchanged.
print(iter(it) is it)
# A string is iterable, but it is not an iterator.
text = iter('ab')
print(next(text))
print(next(text))
The list_iterator holds a position inside the list, so each call to next() reads the following element and moves the position along. Calling iter() on it gives back the very same object, which is why iter(it) is it is True. An iterator is already its own iterator.
The string case is the interesting one. A string is iterable, which means that it can be turned into an iterator, but the string itself is not an iterator. So next('ab') raises a TypeError while next(iter('ab')) works perfectly. That distinction is the reason why iter() is a separate step at all.
For a generator the picture is the same from the outside. Calling iter() on a generator returns the generator itself, and next() resumes it from the place where it was suspended.