Returning Functions from Functions – Closures & Factories in Data Science 2026
One of the most powerful features in Python is the ability for a function to return another function. This pattern, known as function factories or closures, is extremely useful in data science for creating customized transformers, filters, scorers, and reusable data processing pipelines.
TL;DR — Why Return Functions?
- To create reusable, configurable functions
- To implement the "factory" pattern
- To build closures that remember specific settings
- To create decorators and higher-order functions
1. Basic Function Factory Example
def create_price_filter(threshold: float):
"""Returns a function that checks if price > threshold."""
def is_expensive(price: float) -> bool:
return price > threshold # 'threshold' is remembered (closure)
return is_expensive
import numpy as np
import pandas as pd
df = pd.DataFrame({"amount": [250.0, 1800.0, 999.0]})
# Usage
expensive_filter = create_price_filter(1500)
cheap_filter = create_price_filter(500)
df["is_expensive"] = df["amount"].apply(expensive_filter)
df["is_cheap"] = df["amount"].apply(cheap_filter)
2. Real-World Data Science Example
def create_feature_engineer(scaling_method: str = "standard"):
"""Factory that returns a customized feature engineering function."""
def engineer_features(df: pd.DataFrame) -> pd.DataFrame:
df = df.copy()
if scaling_method == "standard":
df["amount_scaled"] = (df["amount"] - df["amount"].mean()) / df["amount"].std()
elif scaling_method == "minmax":
df["amount_scaled"] = (df["amount"] - df["amount"].min()) / (df["amount"].max() - df["amount"].min())
df["log_amount"] = df["amount"].apply(lambda x: 0 if x <= 0 else np.log1p(x))
return df
return engineer_features
# Create different feature engineering pipelines
standard_engineer = create_feature_engineer("standard")
minmax_engineer = create_feature_engineer("minmax")
# Use them
df_standard = standard_engineer(df)
df_minmax = minmax_engineer(df)
3. Best Practices in 2026
- Use function factories when you need to create multiple similar functions with different configurations
- Document what the returned function does and what it "remembers"
- Keep the inner function focused and small
- Use closures for creating reusable data transformers and filters
- Be aware that closures capture variables from the enclosing scope
Closures and Factories
A closure is an inner function that remembers variables from the scope where it was created, even after the outer function has returned. A function that builds and returns such inner functions is called a factory.
Factories are useful for creating families of similar functions, such as scalers with different factors or validators with different limits. They are a light alternative to writing a small class.
Worked Example: A Threshold Factory
We create filter functions with different limits from one factory.
def make_filter(limit: float):
def keep(values):
return [v for v in values if v >= limit]
return keep
keep_50 = make_filter(50)
keep_100 = make_filter(100)
data = [20, 55, 100, 140]
print(keep_50(data))
print(keep_100(data))
print(keep_50.__closure__[0].cell_contents)
funcs = [lambda x, i=i: x + i for i in range(3)]
print([f(10) for f in funcs])
Each returned function keeps its own limit, which you can see in __closure__. The last lines show the i=i default trick, which captures the current loop value instead of the final one.
Practical Tips
Closures are also the building block of decorators. A decorator is a function that takes another function, wraps it in an inner function that adds behaviour such as timing or logging, and returns the wrapper. Understanding closures therefore makes decorators much easier to read.
In data work, factories help when you need many similar transformations, for example one scaling function per unit system or one validation rule per column. Each produced function stays tiny, while the shared logic lives in one place.
Keep factory arguments simple and immutable where possible. If a factory captures a list and that list is later changed elsewhere, every produced function will see the change, which can be surprising. Give produced functions descriptive variable names, such as keep_50, so their configuration stays obvious at the call site.
Common Mistakes
- Expecting closures in loops to capture each loop value automatically.
- Building factories where a plain parameter would do.
- Forgetting to return the inner function.
- Changing captured variables without nonlocal.
Quick Summary
- Closures remember enclosing variables.
- Factories return configured functions.
- Use i=i to capture loop values.
- Keep factories small and focused.
Conclusion
Returning functions from functions (function factories and closures) is a sophisticated but very useful technique in data science. In 2026, this pattern is commonly used to create configurable data preprocessors, custom filters, feature engineers, and model wrappers. When used correctly, it leads to cleaner, more reusable, and more maintainable code.
Next steps:
- Identify repetitive data processing logic in your code and refactor it into a function factory that returns customized functions