Unit 19: Making Your Own Loops
Iterators, generators and promises.
Unit 19 of 31 in Python for kids. Its 4 lessons are How For Loops Really Work, The Easy Way: yield, Promising a Method Exists and Loop Master — below is everything each one explains, and a question or two from it to try.
Every sample on this page was run through real Python before it shipped, and prints exactly what it says it prints.
This unit opens with a fortnight’s trial of everything — no card needed — or with a family plan, bought in the iPhone app. The first two units of every track are free for ever. Try it in the app.
⚙️ How For Loops Really Work
A for loop is asking "what next?"
A for loop does not magically know a list. It keeps asking the thing for its next item, until the thing says there are no more. Anything that can answer those questions can be looped over.
Python
nums = [1, 2, 3]
it = iter(nums)
print(next(it))
print(next(it))
print(next(it))
It prints
1 2 3
Saying "no more"
When there is nothing left, next raises StopIteration — and that is exactly the signal a for loop watches for to know it is finished.
Python
it = iter([1])
print(next(it))
try:
next(it)
except StopIteration:
print("that is the end")
It prints
1 that is the end
Building your own
Give a class __iter__ (which hands back the thing doing the counting) and __next__ (which gives the next item), and for can loop over it like anything else.
Python
class CountTo:
def __init__(self, limit):
self.limit = limit
self.n = 0
def __iter__(self):
return self
def __next__(self):
if self.n >= self.limit:
raise StopIteration
self.n = self.n + 1
return self.n
for number in CountTo(3):
print(number)
It prints
1 2 3
Try it yourself
What tells a for loop to stop?
- Running out of memory
- The iterator raising StopIteration
- Returning None
- The loop counts the items first
What does this print?
Python
it = iter("ab")
print(next(it))
print(next(it))
Answer them in the app
🌱 The Easy Way: yield
All of that, in three lines
A function with yield in it is a generator. It hands out one value and pauses right there, carrying on from the same spot when asked for the next.
Python
def count_to(limit):
n = 1
while n <= limit:
yield n
n = n + 1
for number in count_to(3):
print(number)
It prints
1 2 3
return ends, yield pauses
A return finishes a function for good. A yield steps out for a moment and remembers exactly where it was.
Python
def three():
print("starting")
yield 1
print("woke up again")
yield 2
for n in three():
print(n)
It prints
starting 1 woke up again 2
It only makes what you ask for
This is the real reason generators exist. A list of a million numbers has to exist all at once; a generator makes each one only when it is wanted, so it costs almost no memory.
Python
def forever():
n = 1
while True:
yield n
n = n + 1
for n in forever():
if n > 3:
break
print(n)
It prints
1 2 3
Try it yourself
What makes a function a generator?
- A special decorator
- Having the word yield somewhere inside it
- Returning a list
- Inheriting from Generator
What does this print?
Python
def evens(limit):
for n in range(limit):
if n % 2 == 0:
yield n
print(list(evens(7)))
Answer them in the app
🤞 Promising a Method Exists
A class that refuses to be built
An abstract class is a plan, not a thing. It says "anyone inheriting me must have this method" — and Python will not let you build the plan itself.
Python
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
try:
Shape()
except TypeError:
print("cannot build a plan, only a real shape")
It prints
cannot build a plan, only a real shape
Children must keep the promise
A child that provides the method works fine. One that forgets is refused — and you find out straight away, instead of much later when something calls the missing method.
Python
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side * self.side
print(Square(4).area())
It prints
16
This is abstraction
Once every shape promises an area(), whoever uses them never has to know or care which kind they have. That is abstraction — hiding the details behind a shared promise.
Python
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Square(Shape):
def __init__(self, s):
self.s = s
def area(self):
return self.s * self.s
class Rect(Shape):
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
for shape in [Square(2), Rect(2, 5)]:
print(shape.area())
It prints
4 10
Try it yourself
What is the point of an abstract method?
- It runs before the others
- It forces every child class to provide that method
- It makes the class faster
- It hides the method
Answer it in the app
🏆 Loop Master
Try it yourself
What does this print?
Python
def gen():
yield "a"
yield "b"
it = gen()
print(next(it))
print(next(it))
Answer it in the app