Generators in Python

Generators in Python

What is a Generator?

generator is a special type of iterator that allows you to iterate over a sequence of values without storing them all in memory at once. Generators generate values on-the-fly (lazy evaluation) using the yield keyword.

Key Characteristics

  1. Memory efficient: Generate values one at a time
  2. Lazy evaluation: Values are computed only when needed
  3. Stateful: Remember their state between calls
  4. One-time use: Can only be iterated once

Basic Syntax

python

def generator_function():
    yield value1
    yield value2
    yield value3

Simple Examples

Example 1: Basic Generator Function

python

def simple_generator():
    yield "Hello"
    yield "World"
    yield "!"

# Create generator
gen = simple_generator()

# Iterate through values
print(next(gen))  # Output: Hello
print(next(gen))  # Output: World
print(next(gen))  # Output: !

# Next call would raise StopIteration
# print(next(gen))

Example 2: Using Generator in a Loop

python

def countdown(n):
    while n > 0:
        yield n
        n -= 1

# Use generator
for number in countdown(5):
    print(number)  # Output: 5, 4, 3, 2, 1

Example 3: Number Sequence Generator

python

def number_sequence(limit):
    num = 1
    while num <= limit:
        yield num
        num += 1

# Generate numbers up to 5
for num in number_sequence(5):
    print(num)  # Output: 1, 2, 3, 4, 5

Practical Examples

Example 4: Fibonacci Sequence Generator

python

def fibonacci(limit):
    a, b = 0, 1
    count = 0
    while count < limit:
        yield a
        a, b = b, a + b
        count += 1

# Generate first 7 Fibonacci numbers
for num in fibonacci(7):
    print(num)  # Output: 0, 1, 1, 2, 3, 5, 8

Example 5: File Processing Generator

python

def read_large_file(filename):
    with open(filename, 'r') as file:
        for line in file:
            yield line.strip()

# Process file line by line without loading entire file
# for line in read_large_file('big_file.txt'):
#     process_line(line)

Example 6: Infinite Generator

python

def infinite_counter():
    num = 0
    while True:
        yield num
        num += 1

# Use with caution - infinite loop!
counter = infinite_counter()
print(next(counter))  # 0
print(next(counter))  # 1
print(next(counter))  # 2
# ... continues forever

Generator Expressions

Example 7: Generator Expression (like list comprehension)

python

# List comprehension (eager evaluation)
squares_list = [x*x for x in range(5)]  # [0, 1, 4, 9, 16]

# Generator expression (lazy evaluation)
squares_gen = (x*x for x in range(5))  # Generator object

print(list(squares_gen))  # Convert to list: [0, 1, 4, 9, 16]

Example 8: Using Generator Expressions

python

# Memory efficient for large datasets
large_gen = (x * 2 for x in range(1000000))  # Doesn't create list

# Process one item at a time
for doubled in large_gen:
    if doubled > 100:
        break
    print(doubled)

Advanced Generator Features

Example 9: Generator with send() method

python

def accumulator():
    total = 0
    while True:
        value = yield total
        if value is None:
            break
        total += value

acc = accumulator()
next(acc)  # Start generator (returns 0)

print(acc.send(5))   # Output: 5
print(acc.send(10))  # Output: 15
print(acc.send(3))   # Output: 18

Example 10: Generator with Conditional Yielding

python

def even_numbers(limit):
    for num in range(limit + 1):
        if num % 2 == 0:
            yield num

for even in even_numbers(10):
    print(even)  # Output: 0, 2, 4, 6, 8, 10

Real-World Use Cases

Example 11: Pagination Generator

python

def paginate(items, page_size):
    for i in range(0, len(items), page_size):
        yield items[i:i + page_size]

data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for page in paginate(data, 3):
    print(page)  # [1,2,3], [4,5,6], [7,8,9]

Example 12: Data Processing Pipeline

python

def read_data():
    for i in range(10):
        yield i

def filter_even(numbers):
    for num in numbers:
        if num % 2 == 0:
            yield num

def square(numbers):
    for num in numbers:
        yield num * num

# Chain generators together
pipeline = square(filter_even(read_data()))
for result in pipeline:
    print(result)  # Output: 0, 4, 16, 36, 64

Key Benefits of Generators

  1. Memory Efficiency: Don’t store all values in memory
  2. Lazy Evaluation: Compute values only when needed
  3. Clean Code: Simple syntax for complex iterations
  4. Composability: Can chain generators together
  5. State Preservation: Remember state between calls

When to Use Generators

  • Processing large files or datasets
  • Generating infinite sequences
  • Creating data pipelines
  • When you need memory efficiency
  • For lazy evaluation of expensive computations

Generator vs Normal Function

AspectNormal FunctionGenerator Function
Returnreturnyield
ExecutionRuns to completionPauses and resumes
MemoryStores all resultsGenerates on demand
StateNo state between callsRemembers state
UsageMultiple calls independentSingle iteration

Generators are powerful tools for efficient memory usage and creating complex data processing pipelines!

Generator in Python – Interview Q&A

Basic Concept Questions

1. What is a generator in Python?

Answer:
A generator is a special type of iterator that generates values on-the-fly using the yield keyword. It doesn’t store all values in memory at once, making it memory efficient for large datasets.

Example:

python

def simple_generator():
    yield 1
    yield 2
    yield 3

gen = simple_generator()
print(next(gen))  # 1
print(next(gen))  # 2

2. What is the difference between yield and return?

Answer:

  • return: Terminates function execution and returns a value
  • yield: Pauses function execution, returns a value, and remembers state for next call

Example:

python

def normal_func():
    return 1  # Function ends here

def generator_func():
    yield 1   # Pauses here
    yield 2   # Resumes here on next call

3. How do generators differ from lists?

Answer:

AspectListGenerator
MemoryStores all elementsGenerates on demand
SpeedFaster accessSlower generation
UsageMultiple iterationsSingle iteration
Memory UseHigh for large dataLow for large data

Example:

python

# List - all 1M numbers in memory
list_nums = [x for x in range(1000000)]

# Generator - generates numbers on demand
gen_nums = (x for x in range(1000000))

Technical Questions

4. What happens when a generator function is called?

Answer:
Calling a generator function returns a generator object without executing the function body. Execution only happens when next() is called.

Example:

python

def demo_generator():
    print("Starting")
    yield 1
    print("Continuing")
    yield 2

gen = demo_generator()  # Nothing printed yet
print("Generator created")
print(next(gen))  # "Starting" then 1
print(next(gen))  # "Continuing" then 2

5. How can you create a generator without a function?

Answer:
Using generator expressions (similar to list comprehensions but with parentheses).

Example:

python

# List comprehension
squares_list = [x*x for x in range(5)]  # [0, 1, 4, 9, 16]

# Generator expression
squares_gen = (x*x for x in range(5))  # Generator object
print(list(squares_gen))  # [0, 1, 4, 9, 16]

6. What is the StopIteration exception in generators?

Answer:
StopIteration is automatically raised when a generator has no more values to yield. For-loops catch this exception to terminate.

Example:

python

def simple_gen():
    yield 1

gen = simple_gen()
print(next(gen))  # 1
print(next(gen))  # Raises StopIteration

Implementation Questions

7. How would you create a Fibonacci generator?

Answer:
Example:

python

def fibonacci(limit):
    a, b = 0, 1
    count = 0
    while count < limit:
        yield a
        a, b = b, a + b
        count += 1

for num in fibonacci(8):
    print(num)  # 0, 1, 1, 2, 3, 5, 8, 13

8. How can you send data back to a generator?

Answer:
Using the send() method to pass values back into the generator.

Example:

python

def accumulator():
    total = 0
    while True:
        value = yield total
        if value is None:
            break
        total += value

acc = accumulator()
next(acc)  # Initialize (returns 0)
print(acc.send(5))   # 5
print(acc.send(10))  # 15

Memory and Performance Questions

9. Why are generators memory efficient?

Answer:
Generators produce values one at a time on demand, unlike lists that store all values in memory simultaneously.

Example:

python

# This would use massive memory:
# big_list = [x for x in range(1000000000)]

# This uses minimal memory:
big_gen = (x for x in range(1000000000))

10. When should you use generators over lists?

Answer:
Use generators when:

  • Processing large datasets that don’t fit in memory
  • You only need to iterate once
  • You want lazy evaluation
  • Working with data pipelines

Use lists when:

  • You need random access to elements
  • You need to iterate multiple times
  • The dataset fits comfortably in memory

Advanced Questions

11. How do generators help with infinite sequences?

Answer:
Generators can produce infinite sequences without memory issues since they generate values on demand.

Example:

python

def infinite_counter():
    num = 0
    while True:
        yield num
        num += 1

counter = infinite_counter()
for i in range(5):
    print(next(counter))  # 0, 1, 2, 3, 4

12. How can you chain generators together?

Answer:
Generators can be composed to create data processing pipelines.

Example:

python

def numbers():
    for i in range(10):
        yield i

def evens(seq):
    for num in seq:
        if num % 2 == 0:
            yield num

def squares(seq):
    for num in seq:
        yield num * num

# Chain generators
pipeline = squares(evens(numbers()))
print(list(pipeline))  # [0, 4, 16, 36, 64]

13. What is yield from and how is it used?

Answer:
yield from delegates generation to another generator or iterable.

Example:

python

def first_gen():
    yield 1
    yield 2

def second_gen():
    yield from first_gen()
    yield 3
    yield 4

print(list(second_gen()))  # [1, 2, 3, 4]

Real-World Scenario Questions

14. How would you use generators for large file processing?

Answer:
Example:

python

def process_large_file(filename):
    with open(filename, 'r') as file:
        for line in file:
            # Process one line at a time
            processed_line = line.strip().upper()
            yield processed_line

# Memory efficient for huge files
for line in process_large_file('giant_file.txt'):
    print(line)

15. How can generators be used for pagination?

Answer:
Example:

python

def paginate(items, page_size):
    for i in range(0, len(items), page_size):
        yield items[i:i + page_size]

data = list(range(20))  # [0, 1, 2, ..., 19]
for page in paginate(data, 5):
    print(page)  # [0,1,2,3,4], [5,6,7,8,9], etc.

Best Practices Questions

16. What are the limitations of generators?

Answer:

  • Single use: Can only be iterated once
  • No random access: Can’t access elements by index
  • Stateful: Hard to debug due to paused state
  • Not reusable: Need to recreate for second iteration

17. How can you convert a generator to a list?

Answer:
Use the list() constructor.

Example:

python

def number_gen():
    yield from range(5)

gen = number_gen()
numbers_list = list(gen)  # [0, 1, 2, 3, 4]

18. What’s the difference between a generator and an iterator?

Answer:
All generators are iterators, but not all iterators are generators. Generators are created using functions with yield, while iterators can be any object with __iter__() and __next__() methods.

Error Handling Questions

19. How do you handle errors in generators?

Answer:
Use try-except blocks within the generator function.

Example:

python

def safe_divide(numbers, divisor):
    for num in numbers:
        try:
            yield num / divisor
        except ZeroDivisionError:
            yield "Error: Division by zero"

nums = [10, 20, 30, 40]
result = list(safe_divide(nums, 0))
print(result)  # ['Error: Division by zero', ...]

20. How can you close a generator early?

Answer:
Use the close() method to terminate a generator.

Example:

python

def infinite_gen():
    try:
        num = 0
        while True:
            yield num
            num += 1
    except GeneratorExit:
        print("Generator closed")

gen = infinite_gen()
print(next(gen))  # 0
print(next(gen))  # 1
gen.close()       # "Generator closed"

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *