Functions as Return Values in Python 2026

Writing Functions Mar 16, 2026

Functions as Return Values in Python 2026

Python allows functions to return other functions. This powerful pattern is the foundation of factory functions, closures, decorators, and many elegant design solutions. Returning functions gives you the ability to create customized behavior dynamically.

TL;DR — Key Takeaways 2026

  • A function can return another function as its result
  • The returned function often "remembers" values from the outer scope (closure)
  • This pattern is widely used for factory functions, decorators, and configuration
  • Use type hints with Callable for clarity

1. Basic Example


def make_greeter(greeting: str):
    """Returns a customized greeting function."""
    
    def greeter(name: str) -> str:
        return f"{greeting}, {name}!"
    
    return greeter

# Create specialized functions
hello = make_greeter("Hello")
welcome = make_greeter("Welcome")
good_morning = make_greeter("Good morning")

print(hello("Alice"))          # Hello, Alice!
print(welcome("Bob"))          # Welcome, Bob!
print(good_morning("Charlie")) # Good morning, Charlie!

2. Practical Factory with Closure


from typing import Callable

def create_power_function(exponent: int) -> Callable[[int], int]:
    """Factory that returns a power function for any exponent."""
    
    def power(base: int) -> int:
        return base ** exponent
    
    return power

square = create_power_function(2)
cube   = create_power_function(3)
power4 = create_power_function(4)

print(square(5))   # 25
print(cube(3))     # 27
print(power4(2))   # 16

3. Best Practices in 2026

  • Use clear, descriptive names for the returned functions
  • Document what the returned function expects and returns
  • Combine with closures to "remember" configuration values
  • Use Callable[[ArgTypes], ReturnType] type hints
  • Keep the inner function focused and small

Functions That Build Functions

A function can return another function. The returned function keeps access to the variables of the function that created it, so you can use one factory to build many specialised functions, such as converters with different rates or validators with different limits.

This pattern is called a function factory. It is a lightweight alternative to a class when the object only needs one behaviour. The standard library uses it too: operator.itemgetter and functools.partial both return callables built from your arguments.

Give the returned function a meaningful name inside the factory, and consider setting __name__ or using functools.wraps when it wraps another function, so tracebacks and logs stay readable.

If the factory needs many settings or several methods, a class is usually the clearer choice.

Worked Example: A Factory for Unit Converters

We build several converter functions from one factory and store them in a dictionary.

def make_converter(factor: float, unit: str):
    def convert(value: float) -> str:
        return f"{value * factor:.2f} {unit}"
    convert.__name__ = f"to_{unit}"
    return convert

to_km = make_converter(1.609, "km")
to_kg = make_converter(0.4536, "kg")

print(to_km(10), "|", to_kg(10))
print(to_km.__name__, to_kg.__name__)

CONVERTERS = {name: make_converter(f, name) for name, f in [("km", 1.609), ("kg", 0.4536)]}
print(CONVERTERS["kg"](2.5))

The two converters print 16.09 km and 4.54 kg, each remembering its own factor and unit. Their names, to_km and to_kg, make them easy to tell apart in logs, and the dictionary version converts 2.5 to 1.13 kg.

Practical Tips

Factories work well for families of similar functions, such as validators with different limits, formatters for different currencies or loaders for different file types.

Give the factory parameters clear names and defaults, and validate them when the factory runs, not when the returned function runs. Errors then point at the configuration, not at some later call.

When the returned function wraps another function, use functools.wraps so its name and docstring stay meaningful.

If you find yourself adding more and more parameters, consider a dataclass with a __call__ method.

Test a factory by building two functions with different settings and checking that each keeps its own values. This catches accidental shared state, such as a mutable default argument used by every function the factory creates.

Keep factories near the code that uses their results.

Common Mistakes

  • Returning convert() instead of convert.
  • Building many factories where one class would be clearer.
  • Leaving all returned functions with the same name in tracebacks.
  • Creating functions in a loop that all share the loop variable.

Quick Summary

  • A factory returns a configured function.
  • Each result remembers its own settings.
  • Set names or use functools.wraps.
  • Switch to a class for complex state.

Conclusion

Returning functions from other functions is a fundamental and elegant Python technique. In 2026, this pattern is heavily used for creating flexible APIs, decorators, configuration factories, and reusable behavior. Mastering it will make your code significantly more powerful and expressive.

Next steps:

  • Look for opportunities where you repeat similar logic and replace it with a function factory

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