Iterators and Generators

Back

Loading concept...

๐ŸŽญ The Magic Conveyor Belt: Understanding Iterators and Generators

Imagine you have a magical candy factory. Instead of making all the candies at once and filling up your entire room, this factory makes one candy at a time, only when you ask for it. Thatโ€™s exactly what iterators and generators do in Python!


๐ŸŽช The Big Picture

Think of a conveyor belt at a factory:

  • Regular lists = All items dumped on the floor at once (takes up space!)
  • Iterators/Generators = Items come one by one on a conveyor belt (saves space!)
graph TD A["๐Ÿ“ฆ Your Data"] --> B{How do you want it?} B -->|All at once| C["๐Ÿ—ƒ๏ธ List - Uses lots of memory"] B -->|One at a time| D["๐ŸŽข Iterator - Saves memory!"]

๐Ÿ“œ The Iterator Protocol

What is it?

The Iterator Protocol is like a promise between Python and your object. It says:

โ€œI promise I can give you items one at a time!โ€

To keep this promise, your object needs TWO special methods:

Method What it does
__iter__() Says โ€œIโ€™m ready to start giving items!โ€
__next__() Gives the next item (or says โ€œIโ€™m done!โ€)

Simple Example

# A list is iterable
my_list = [1, 2, 3]

# Get the iterator (the conveyor belt)
my_iterator = iter(my_list)

# Get items one by one
print(next(my_iterator))  # 1
print(next(my_iterator))  # 2
print(next(my_iterator))  # 3
# next again? StopIteration!

๐ŸŽฏ Key Insight: When there are no more items, Python raises StopIteration - like the conveyor belt saying โ€œThatโ€™s all folks!โ€


๐Ÿ”ง Creating Custom Iterators

Letโ€™s build our own conveyor belt!

The Countdown Timer

Imagine a rocket countdown: 5, 4, 3, 2, 1, Blast off!

class Countdown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self  # I am my own iterator!

    def __next__(self):
        if self.current <= 0:
            raise StopIteration  # Done!

        self.current -= 1
        return self.current + 1

# Use it!
for num in Countdown(5):
    print(num)
# Output: 5, 4, 3, 2, 1

Why Custom Iterators?

Benefit Explanation
๐Ÿ’พ Memory efficient Only one item in memory at a time
โ™พ๏ธ Infinite sequences Can represent endless data
๐ŸŽฎ Full control You decide what comes next

โšก Generator Functions - The Easier Way!

Custom iterators work, but theyโ€™re like building a car from scratch. Generators are like buying a car - much easier!

The Magic Word: yield

Instead of return, use yield. Itโ€™s like saying:

โ€œHereโ€™s one item. Call me again for the next one!โ€

def countdown(start):
    while start > 0:
        yield start  # Give this, then pause
        start -= 1

# Use it the same way!
for num in countdown(5):
    print(num)
# Output: 5, 4, 3, 2, 1

yield vs return

graph TD A["Function Called"] --> B{return or yield?} B -->|return| C["Function ends forever ๐Ÿ’€"] B -->|yield| D["Function pauses โธ๏ธ"] D --> E["Next call? Resume! โ–ถ๏ธ"]
return yield
Exits function forever Pauses function
Gives one value Can give many values
Memory: all at once Memory: one at a time

๐ŸŽฏ The yield Statement Deep Dive

How yield Works

Think of yield as a bookmark in a book:

  1. You read until the bookmark
  2. You close the book (pause)
  3. Later, you open it and continue from the bookmark!
def story_teller():
    yield "Once upon a time..."
    yield "There was a Python..."
    yield "The end!"

story = story_teller()
print(next(story))  # Once upon a time...
print(next(story))  # There was a Python...
print(next(story))  # The end!

Generators Remember Their State!

def counter():
    count = 0
    while True:
        count += 1
        yield count

# This generator remembers!
c = counter()
print(next(c))  # 1
print(next(c))  # 2
print(next(c))  # 3
# It never forgets where it was!

๐Ÿ”— yield from - The Delegation Master

Sometimes you want to yield items from another iterable. Instead of:

def nested():
    for i in [1, 2, 3]:
        yield i
    for i in [4, 5, 6]:
        yield i

Use yield from:

def nested():
    yield from [1, 2, 3]
    yield from [4, 5, 6]

Real Example: Flattening Nested Lists

def flatten(nested_list):
    for item in nested_list:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item

# Flatten this mess!
messy = [1, [2, 3], [4, [5, 6]]]
print(list(flatten(messy)))
# Output: [1, 2, 3, 4, 5, 6]

๐ŸŽฏ yield from = โ€œHey, let this other thing yield for me!โ€


๐Ÿ’จ Generator Expressions - One-Liners!

Remember list comprehensions? Generator expressions are their memory-efficient cousins!

Syntax Comparison

# List comprehension (creates entire list)
squares_list = [x**2 for x in range(5)]

# Generator expression (creates one at a time)
squares_gen = (x**2 for x in range(5))

Spot the difference? [ ] vs ( )

When to Use Which?

Use Case List [ ] Generator ( )
Small data โœ… Great โœ… Works
Huge data โŒ Memory! โœ… Perfect
Need to reuse โœ… Yes โŒ One-time only
Need length โœ… len() works โŒ Must iterate

Example: Sum of Millions

# Bad: Creates 1 million numbers in memory
total = sum([x for x in range(1000000)])

# Good: One number at a time!
total = sum(x for x in range(1000000))

๐Ÿงฐ The itertools Module - Your Superpower Toolkit!

Pythonโ€™s itertools is like a Swiss Army knife for iterators. Itโ€™s in the standard library - no install needed!

import itertools

๐Ÿ”ข Infinite Iterators

# count: 1, 2, 3, 4, ... forever!
for i in itertools.count(1):
    if i > 5: break
    print(i)  # 1, 2, 3, 4, 5

# cycle: A, B, C, A, B, C, A... forever!
colors = itertools.cycle(['red', 'green', 'blue'])
for _ in range(5):
    print(next(colors))

# repeat: same thing, forever (or n times)
for x in itertools.repeat("Hello", 3):
    print(x)  # Hello Hello Hello

๐Ÿ”— Combining Iterators

# chain: connect multiple iterables
letters = itertools.chain("AB", "CD", "EF")
print(list(letters))  # ['A','B','C','D','E','F']

# zip_longest: zip with fill value
names = ['Alice', 'Bob']
ages = [25, 30, 35]
result = itertools.zip_longest(names, ages, fillvalue='?')
print(list(result))
# [('Alice',25), ('Bob',30), ('?',35)]

โœ‚๏ธ Filtering Iterators

# takewhile: take until condition fails
nums = [1, 3, 5, 7, 2, 4, 6]
small = itertools.takewhile(lambda x: x < 6, nums)
print(list(small))  # [1, 3, 5]

# dropwhile: skip until condition fails
big = itertools.dropwhile(lambda x: x < 6, nums)
print(list(big))  # [7, 2, 4, 6]

# filterfalse: opposite of filter
evens = itertools.filterfalse(
    lambda x: x % 2, range(10)
)
print(list(evens))  # [0, 2, 4, 6, 8]

๐ŸŽฐ Combinatoric Iterators

# permutations: all arrangements
perms = itertools.permutations('ABC', 2)
print(list(perms))
# [('A','B'),('A','C'),('B','A'),
#  ('B','C'),('C','A'),('C','B')]

# combinations: unique groups (order doesn't matter)
combs = itertools.combinations('ABC', 2)
print(list(combs))
# [('A','B'), ('A','C'), ('B','C')]

# product: cartesian product (like nested loops)
prod = itertools.product([1,2], ['a','b'])
print(list(prod))
# [(1,'a'), (1,'b'), (2,'a'), (2,'b')]

๐ŸŽ“ Quick Reference Table

Tool Purpose Memory
iter() Get iterator from iterable -
next() Get next item -
Custom Iterator Full control Efficient
Generator Function Easy creation with yield Efficient
yield from Delegate to sub-iterator Efficient
Generator Expression One-liner generators Efficient
itertools Pre-built iterator tools Efficient

๐ŸŒŸ The Golden Rule

โ€œDonโ€™t load what you donโ€™t need.โ€

If youโ€™re processing 1 million items but only need to look at them one at a time, use iterators and generators. Your computerโ€™s memory will thank you!

graph TD A["Need to process data?"] --> B{How much?} B -->|Small| C["List is fine โœ…"] B -->|Large/Infinite| D["Use Generators! ๐Ÿš€"] D --> E["๐Ÿ’พ Memory saved!"] D --> F["โšก Faster start!"] D --> G["โ™พ๏ธ Can be infinite!"]

๐ŸŽ‰ You Did It!

You now understand:

  • โœ… The Iterator Protocol (__iter__ and __next__)
  • โœ… Creating Custom Iterators
  • โœ… Generator Functions with yield
  • โœ… The yield from delegation
  • โœ… Generator Expressions (x for x in ...)
  • โœ… The itertools module superpowers

Remember: Generators are like a patient chef who cooks one dish at a time, rather than preparing everything and running out of counter space. Smart, efficient, and elegant!

Now go forth and generate some amazing code! ๐Ÿš€

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.