Python Beyond the Basics: Refactoring Object-Oriented Patterns into Functional Power

Overview

Functions in

are far more than mere containers for reusable code blocks. They are first-class citizens, meaning you can pass them as arguments, return them from other functions, and assign them to variables. This guide explores how to refactor a traditional, class-heavy
Strategy Pattern
into a leaner, functional architecture using
Closures
and
Partial Function Application
. By moving away from rigid class structures, you can reduce boilerplate and increase the flexibility of your software design.

Prerequisites

To follow this tutorial, you should have a solid grasp of basic Python syntax and the fundamentals of Object-Oriented Programming (OOP). Familiarity with

and the concept of interfaces or abstract base classes will help you understand the refactoring process from classes to functions.

Python Beyond the Basics: Refactoring Object-Oriented Patterns into Functional Power
You Can Do Really Cool Things With Functions In Python

Key Libraries & Tools

  • typing: Specifically the Callable and List types to define function signatures.
  • functools: Specifically the partial utility, which enables the creation of new functions from existing ones with pre-filled arguments.

Code Walkthrough

1. Defining the Function Type

Instead of a protocol class, we define a clear type alias using Callable. This ensures any strategy function we write takes a list of integers (prices) and returns a boolean.

from typing import Callable, List

TradingStrategy = Callable[[List[int]], bool]

2. The Closure Approach

Closures allow us to "bake" configuration into a function. By wrapping our strategy logic inside an outer function, the inner function retains access to the outer variables even after the outer function has finished execution.

def should_buy_average_closure(window_size: int) -> TradingStrategy:
    def should_buy(prices: List[int]) -> bool:
        # Accesses window_size from the outer scope
        return sum(prices[-window_size:]) / window_size > prices[-1]
    return should_buy

3. Partial Function Application

While closures work, they are verbose.

offers a cleaner alternative. It allows you to take a function that requires multiple arguments and return a new function with some of those arguments already set.

from functools import partial

def should_sell_minmax(prices: List[int], max_price: int) -> bool:
    return prices[-1] > max_price

# Create a specific strategy with a pre-set max_price
sell_strategy = partial(should_sell_minmax, max_price=35000)

Syntax Notes

Python allows the use of underscores in numeric literals (e.g., 35_000) to improve readability. This has no impact on the value but makes it significantly easier to count zeros in high-frequency trading contexts. Additionally, when using Callable, the first argument is a list of input types, and the second is the return type.

Practical Examples

These techniques are ideal for trading bots where you need to swap out logic frequently. You can mix and match a "Buy" strategy from an average-based model with a "Sell" strategy from a min-max model without creating complex class hierarchies. This also applies to data processing pipelines where transformation steps need specific configurations.

Tips & Gotchas

Avoid confusing

with partial application. Currying breaks a multi-argument function into a chain of single-argument functions, whereas partial application simply fixes a few arguments at once. Always use type hints with your partial functions to ensure the calling code remains aware of the expected input types.

3 min read