Amar Prakash Pandey - ᕦ(ò_óˇ)ᕤ

Currying and Partial Application: Shaping Intent

banner

Why this topic is confusing

Currying and partial application are concepts that almost every Python developer has seen, but very few feel confident using. For a long time, I understood what they did, but I didn’t know when using them actually made code better.


A clear distinction: currying vs partial application

Currying transforms a function that takes multiple arguments into a chain of functions that each take a single argument:

def multiply(a):
    def by(b):
        return a * b
    return by

# Usage: multiply(3)(4) returns 12
triple = multiply(3)
result = triple(4)  # 12

Partial application fixes some arguments of an existing function and returns a new callable:

from functools import partial

def multiply(a, b):
    return a * b

# Create specialized functions
double = partial(multiply, 2)
triple = partial(multiply, 3)

print(double(5))  # 10
print(triple(4))  # 12

Currying changes the shape of a function; partial application configures a function.


Where functools.partial actually helps

Partial application became useful to me once I started seeing it as a way to name behavior.

from functools import partial

# Instead of repeating the same sorting logic
fruits = ["apple", "banana", "cherry", "date"]
words = ["hello", "world", "python", "code"]

# Create reusable, named behaviors
sort_by_length = partial(sorted, key=len)
sort_reverse = partial(sorted, reverse=True)

print(sort_by_length(fruits))   # ['date', 'apple', 'banana', 'cherry']
print(sort_reverse(words))      # ['world', 'python', 'hello', 'code']

sort_by_length and sort_reverse communicate purpose clearly - no mental parsing required.


Reducing lambda noise

Partial application helps clean up map and filter operations:

from functools import partial

def add_tax(price, tax_rate):
    return price * (1 + tax_rate)

prices = [100, 200, 150, 300]

# Before - lambda creates noise and repetition
with_sales_tax = list(map(lambda p: add_tax(p, 0.08), prices))

# After - partial creates clarity and reusability
add_sales_tax = partial(add_tax, tax_rate=0.08)
with_sales_tax = list(map(add_sales_tax, prices))

print(with_sales_tax)  # [108.0, 216.0, 162.0, 324.0]

The add_sales_tax function can be reused anywhere, and the intent is crystal clear.


Configuration now, execution later

One of the most practical uses of partial is separating configuration from execution:

import logging
from functools import partial

# Set up logger
logger = logging.getLogger("PaymentService")

# Configure different log levels with context
log_payment_error = partial(logger.error, extra={"service": "payment"})
log_payment_info = partial(logger.info, extra={"service": "payment"})

# Usage throughout your code - clean and consistent
def process_payment(amount):
    try:
        # payment logic here
        log_payment_info(f"Payment processed: ${amount}")
    except Exception as e:
        log_payment_error(f"Payment failed: {e}")
        
# Another example: API client configuration
import requests
from functools import partial

# Configure once
api_get = partial(requests.get, timeout=30, headers={"User-Agent": "MyApp/1.0"})
api_post = partial(requests.post, timeout=30, headers={"User-Agent": "MyApp/1.0"})

# Use everywhere without repeating configuration
response = api_get("https://api.example.com/users")

This keeps call sites clean and configuration centralized.


When NOT to use these tools

Don’t use partial application when it makes code harder to understand:

# BAD - Over-engineered for simple cases
from functools import partial
add_one = partial(lambda x, y: x + y, 1)
result = add_one(5)  # Just write: result = 5 + 1

# BAD - Too many layers of abstraction
process_data = partial(partial(map, str.upper), ["hello", "world"])

# GOOD - Simple and direct
data = ["hello", "world"]
result = [item.upper() for item in data]

Use partial application when it names intent and reduces repetition. Skip it when a direct approach is clearer.


Lastly, thank you for reading this post. For more awesome posts, you can explore my other articles here, and follow me on Github — amarlearning.

#python #functional-programming #clean-code #refactoring #software-design

Comments