Iterators, Generators & Decorators
Produce values lazily one at a time with yield, and wrap extra behaviour around functions with decorators.
What you will learn
- Write a generator with yield
- Understand lazy, memory-friendly iteration
- Read and write a simple decorator
The problem generators solve
Suppose you need the numbers 1 to ten million. Building a full list of them stores every value in memory at once — wasteful if you only ever look at one at a time. A generator produces values lazily: it hands you one value, pauses, and only computes the next when you ask. “Lazy” here means “does no work until needed.”
yield makes a generator
A generator looks like a normal function but uses yield instead of return. Each yield produces one value and pauses the function, remembering exactly where it was; the next request resumes from there.
def count_up_to(n):
i = 1
while i <= n:
yield i # produce one value, then pause
i = i + 1
for number in count_up_to(3):
print(number)When the for loop asks for a value, count_up_to runs until it hits yield i, hands back i, and freezes. On the next loop turn it wakes up right after the yield, bumps i, and yields again — giving 1, then 2, then 3. It never builds a list; it makes each number on demand. For three values that is tiny, but for ten million it saves enormous memory.
Note: Output: 1 2 3
Tip: Any function with a yield in it is automatically a generator. Calling it does not run the body — it hands back a generator object that runs a little more each time you loop or call next() on it.
Decorators: wrap a function
A decorator is a function that wraps another function to add behaviour around it — without editing the original. This builds on two ideas you already know: a function is a value you can pass around, and a function can be defined inside another. Decorators power things like Flask and Django route definitions, so they are worth recognising.
def shout(func):
def wrapper():
result = func()
return result.upper() + "!"
return wrapper
@shout
def greet():
return "hello"
print(greet())Reading this: shout takes a function func and returns a new wrapper function that calls func, then uppercases its result and adds "!". The @shout line above greet is shorthand for “replace greet with shout(greet).” So when we call greet(), we are really running the wrapper: it calls the original ("hello"), transforms it, and returns HELLO!. The original greet was never edited — the decorator added behaviour around it.
Note: Output: HELLO!
In short, the two tools in this lesson both let you do more with less code:
- A generator (
yield) produces a sequence lazily, one item at a time, saving memory. - A decorator (
@name) wraps a function to add behaviour around it, without changing the function itself.
Q. What keyword turns a function into a generator?
✍️ Practice
- Write a generator that yields the even numbers up to a limit.
- Write a decorator that prints "calling..." before running the wrapped function.
🏠 Homework
- Write a generator fibonacci(n) that yields the first n Fibonacci numbers, and loop over it to print them.