Chapter Summary

Chapter Summary

Key Points

  • 1.

    Design functions for composability. Pure functions with explicit inputs and outputs are easier to test, debug, and parallelize than stateful procedures. Use None for mutable defaults, keyword-only arguments (*) for safety-critical parameters, and NumPy-style docstrings for self-documenting APIs.

  • 2.

    Closures create parameterized function families. Function factories use closures to capture configuration (noise levels, regularization strengths, kernel bandwidths) at creation time, producing specialized callables without class boilerplate. Remember Python's late-binding rule: closures capture references, not values — use default arguments or functools.partial for loop variables.

  • 3.

    Decorators add reusable cross-cutting behavior. The @decorator syntax is sugar for f = decorator(f). Always use @functools.wraps to preserve metadata. Stacking order is bottom-up at definition time, top-down at call time. The most useful scientific decorators are @timer, @cache_result, and @log_shape.

  • 4.

    Context managers guarantee resource cleanup. The with statement calls __enter__ / __exit__ and ensures cleanup even on exceptions. Use contextlib.contextmanager for simple generator-based context managers. Key use cases: timing blocks, GPU memory tracking, temporary RNG seeds, and file/database connections.

  • 5.

    These patterns compose. Closures enable decorators (a decorator is a closure that wraps a function). Context managers can be built with decorators (@contextmanager). Function factories produce the specialized callables that decorators and context managers operate on. Mastering these building blocks unlocks the design patterns used throughout NumPy, PyTorch, and scikit-learn.

Looking Ahead

Chapter 3 takes these functional building blocks and scales them up with classes and object-oriented design for scientific code. You will learn to design class hierarchies for simulation components, use __slots__ for memory-efficient data containers, implement the iterator and descriptor protocols, and combine OOP with the functional patterns from this chapter.