Using Generator Functions in Python – Practical Patterns for Data Science 2026

Data Science Tool Box Mar 18, 2026

Using Generator Functions in Python – Practical Patterns for Data Science 2026

Generator functions (using yield) are one of the most powerful tools for writing memory-efficient and clean data processing code. Once you learn how to use them effectively, they become essential for handling large datasets, streaming data, and building reusable pipelines.

TL;DR — How to Use a Generator Function

  • Call the function like a normal function
  • It returns a generator object (lazy iterator)
  • Consume it with for loop, sum(), next(), or list()

1. Basic Usage

def high_value_sales(records):
    for row in records:
        if row["amount"] > 1500:
            yield row

# Usage
sales_data = [{"customer_id": 101, "amount": 2300}, {"customer_id": 102, "amount": 900}]

for sale in high_value_sales(sales_data):
    print(f"High value sale by customer {sale['customer_id']}: ${sale['amount']}")

2. Real-World Data Science Patterns

import pandas as pd

# Create a small demo file so this example runs as-is
# (small stand-in for sales_data.csv; in your project this is your real large file)
import numpy as np
rng = np.random.default_rng(42)
n = 60
pd.DataFrame({
    "order_id": range(1001, 1001 + n),
    "order_date": pd.date_range("2026-01-01", periods=n, freq="3D"),
    "customer_id": rng.integers(101, 121, n),
    "customer_name": rng.choice([" ana", "ben ", "chen", "dana"], n),
    "product_id": rng.integers(1, 9, n),
    "region": rng.choice(["North", "South", "East", "West"], n),
    "category": rng.choice(["Electronics", "Computers", "Books", "Home"], n),
    "payment_method": rng.choice(["card", "cash", "wallet"], n),
    "segment": rng.choice(["consumer", "business"], n),
    "quantity": rng.integers(1, 6, n),
    "amount": rng.uniform(50, 2500, n).round(2),
    "discount": rng.choice([0.0, 0.05, 0.1, 0.2], n),
}).assign(
    price=lambda d: (d["amount"] / d["quantity"]).round(2),
    profit=lambda d: (d["amount"] * rng.uniform(0.05, 0.3, n)).round(2),
).assign(
    ship_date=lambda d: d["order_date"] + pd.Timedelta(days=2),
    status=lambda d: np.where(d["amount"] > 2000, "priority", "standard"),
).to_csv("sales_data.csv", index=False)
df = pd.read_csv("sales_data.csv")

# Pattern 1: Chunked processing
def process_in_chunks(df, chunk_size=50000):
    for start in range(0, len(df), chunk_size):
        chunk = df.iloc[start:start+chunk_size]
        yield chunk

for chunk in process_in_chunks(df):
    chunk["profit"] = chunk["amount"] * 0.25
    print(f"Processed {len(chunk)} rows")

# Pattern 2: Feature engineering pipeline
def enrich_sales_data(df):
    for row in df.itertuples():
        profit = row.amount * 0.25
        yield {
            "customer_id": row.customer_id,
            "amount": row.amount,
            "profit": round(profit, 2),
            "category": "Premium" if profit > 500 else "Standard",
            "log_amount": round(row.amount ** 0.5, 2) if row.amount > 0 else 0
        }

# Consume the generator
enriched = list(enrich_sales_data(df))   # Only if you need the full list
enriched_df = pd.DataFrame(enriched)

3. Advanced Consumption Patterns

# Sum without loading everything
total_profit = sum(row["profit"] for row in enrich_sales_data(df))

# Get first N items
import itertools
top_10 = list(itertools.islice(enrich_sales_data(df), 10))

# Manual control with next()
gen = enrich_sales_data(df)
first_record = next(gen)

4. Best Practices in 2026

  • Use generator functions for any processing that can be done row-by-row or chunk-by-chunk
  • Keep generators stateless and pure when possible
  • Use them inside other functions or pipelines
  • Document what the generator yields
  • Convert to list only when you really need random access or multiple passes

Consuming Generators

Generators can be consumed in several ways: a for loop, built-ins like sum and max, list() to materialise everything, next() for single items, and itertools tools such as islice to take only part.

Choose the consumer that matches your need. If you only need the first ten results, islice stops the generator early and saves all the work for the rest. If you need everything twice, materialise once into a list.

Generators can also feed each other, forming pipelines where each stage does one small job.

Worked Example: Taking Only What You Need

An infinite generator is consumed safely with islice and filtered in a pipeline.

from itertools import islice

def order_ids(start=1000):
    n = start
    while True:
        yield n
        n += 1

def even(ids):
    for i in ids:
        if i % 2 == 0:
            yield i

first_five = list(islice(order_ids(), 5))
print(first_five)

first_even = list(islice(even(order_ids(1001)), 3))
print(first_even)

print(sum(islice(order_ids(1), 10)))

The generator never ends, but islice takes only what is requested, so the program finishes instantly. The pipeline yields 1002, 1004 and 1006, and the sum of the first ten ids from 1 is 55.

Common Mistakes

  • Calling list() on an infinite generator.
  • Materialising large generators when a sum is all you need.
  • Forgetting that each consumer uses up the generator.
  • Building deep pipelines without testing each stage.

Quick Summary

  • Consume with for, sum, list, next or islice.
  • islice limits how much work is done.
  • Chain generators into pipelines.
  • Materialise once if you need reuse.

Conclusion

Generator functions are the professional way to handle large-scale data processing in Python. In 2026 data science projects, they allow you to write clean, reusable, and extremely memory-efficient code. Master the pattern of building and consuming generator functions, and you will be able to process datasets of almost any size without running out of memory.

Next steps:

  • Convert one of your current data processing loops into a generator function and integrate it into your workflow

Related Tutorials

Share:
Last updated: March 2026
Built-in Functions in Python 2026 str() in Python 2026: String Conversion + Modern Formatting & Best Practices memoryview with JAX in Python 2026: Zero-Copy NumPy → JAX Array Interop + Efficient ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + GPU Pinning & ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + ML Examples memoryview with NumPy & PyTorch in Python 2026: Zero-Copy Views, Efficient Slicing & ML Interop Examples memoryview with NumPy in Python 2026: Zero-Copy Views, Efficient Slicing & Real ML Examples memoryview() in Python 2026: Zero-Copy Magic for Large Binary Data + Real Examples import() in Python 2026: How It Works, Security Risks & Modern Alternatives (importlib) zip() in Python 2026: How to Use It, strict=True Behavior & Real-World Examples vars() in Python 2026: Accessing Object Namespace + Modern Introspection Patterns type() in Python 2026: Dynamic Type Inspection & Object Creation + Modern Patterns tuple() in Python 2026: Immutable Sequences + Modern Patterns & Best Practices super() in Python 2026: Method Resolution & Modern Inheritance Patterns sum() in Python 2026: Summing Iterables + Modern Numeric Patterns & Best Practices staticmethod() in Python 2026: Static Methods, Utility Functions & Modern Best Practices sorted() in Python 2026: Sorting Iterables + Modern Patterns & Best Practices slice() in Python 2026: Creating Slice Objects + Modern Patterns & Best Practices setattr() in Python 2026: Dynamic Attribute Setting + Modern Patterns & Safety set() in Python 2026: Mutable Sets Creation + Modern Patterns & Best Practices