functools.partial¶
partial freezes some arguments of a function, creating a new callable with fewer parameters. It's the standard way to do partial function application in Python.
from functools import partial
Basic Usage¶
from functools import partial
def power(base, exponent):
return base ** exponent
# Freeze keyword argument
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5)) # 25
print(cube(5)) # 125
# Freeze positional argument
double = partial(pow, 2) # pow(2, x)
print(double(8)) # 256 (2^8)
print(double(10)) # 1024 (2^10)
How partial Works¶
partial(func, *args, **kwargs) returns a new callable that:
- Prepends
argsto the positional arguments - Merges
kwargswith keyword arguments (call-time kwargs override)
from functools import partial
def greet(greeting, name, punctuation="!"):
return f"{greeting}, {name}{punctuation}"
# Freeze 'greeting' positionally
hello = partial(greet, "Hello")
print(hello("Alice")) # Hello, Alice!
print(hello("Bob", punctuation=".")) # Hello, Bob.
# Freeze keyword argument
casual = partial(greet, punctuation="~")
print(casual("Hey", "Alice")) # Hey, Alice~
Inspecting partial Objects¶
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
print(square.func) # <function power at 0x...>
print(square.args) # ()
print(square.keywords) # {'exponent': 2}
double = partial(pow, 2)
print(double.func) # <built-in function pow>
print(double.args) # (2,)
print(double.keywords) # {}
partial vs lambda¶
from functools import partial
# These produce the same result:
f1 = partial(pow, 2)
f2 = lambda x: pow(2, x)
print(f1(10)) # 1024
print(f2(10)) # 1024
Why Prefer partial¶
| Feature | partial |
lambda |
|---|---|---|
| Readability | Clear intent | Can be cryptic |
| Introspection | .func, .args, .keywords |
Opaque |
| Pickling | Picklable | Not picklable |
| Performance | Slightly faster | Slight overhead |
| Late binding | No — arguments frozen at creation | Yes — evaluated at call time |
from functools import partial
# Late binding gotcha with lambda
funcs = [lambda x: x + i for i in range(3)]
print([f(0) for f in funcs]) # [2, 2, 2] — all use i=2!
# partial avoids this
funcs = [partial(lambda x, i: x + i, i=i) for i in range(3)]
print([f(0) for f in funcs]) # [0, 1, 2] — correct!
When lambda Is Better¶
# Complex transformations that aren't simple argument freezing
transform = lambda x: x.strip().lower().replace(" ", "_")
# Conditional logic
process = lambda x: x * 2 if x > 0 else 0
# These can't be expressed cleanly with partial
Practical Examples¶
Customizing Built-in Functions¶
from functools import partial
# Custom print
debug_print = partial(print, "[DEBUG]", end="\n\n")
debug_print("Starting process")
# [DEBUG] Starting process
error_print = partial(print, "[ERROR]", flush=True)
error_print("Something failed")
# [ERROR] Something failed
# Custom int parsing
parse_binary = partial(int, base=2)
parse_hex = partial(int, base=16)
print(parse_binary("1010")) # 10
print(parse_hex("FF")) # 255
Sorting with Fixed Keys¶
from functools import partial
from operator import itemgetter
# Sort list of dicts by specific key
sort_by_name = partial(sorted, key=itemgetter('name'))
sort_by_age = partial(sorted, key=itemgetter('age'))
users = [
{'name': 'Charlie', 'age': 30},
{'name': 'Alice', 'age': 25},
{'name': 'Bob', 'age': 35},
]
print(sort_by_name(users))
# [{'name': 'Alice', ...}, {'name': 'Bob', ...}, {'name': 'Charlie', ...}]
print(sort_by_age(users))
# [{'name': 'Alice', ...}, {'name': 'Charlie', ...}, {'name': 'Bob', ...}]
Callback Registration¶
from functools import partial
def handle_event(event_type, data, timestamp=None):
print(f"[{event_type}] {data} at {timestamp}")
# Register specialized handlers
on_click = partial(handle_event, "CLICK")
on_hover = partial(handle_event, "HOVER")
on_submit = partial(handle_event, "SUBMIT")
on_click("Button A", timestamp="12:00")
# [CLICK] Button A at 12:00
Configuring API Clients¶
from functools import partial
import json
def api_request(method, url, headers=None, timeout=30):
"""Simulated API request."""
return f"{method} {url} (timeout={timeout})"
# Create pre-configured request functions
auth_headers = {"Authorization": "Bearer TOKEN"}
api_get = partial(api_request, "GET", headers=auth_headers)
api_post = partial(api_request, "POST", headers=auth_headers)
fast_get = partial(api_get, timeout=5)
print(api_get("/users"))
# GET /users (timeout=30)
print(fast_get("/health"))
# GET /health (timeout=5)
Mathematical Functions¶
from functools import partial
import math
# Logarithms with fixed base
log2 = partial(math.log, base=2) # Actually: math.log(x, base=2)
log10 = partial(math.log, base=10)
# Note: math.log signature is log(x, base=e)
# So we need keyword argument for base
log2 = partial(math.log, 2) # This is log(2, x) — wrong!
# Better approach for positional-only built-ins:
log2 = lambda x: math.log(x, 2) # Use lambda here
log10 = lambda x: math.log(x, 10)
# Rounding with fixed precision
round2 = partial(round, ndigits=2)
round4 = partial(round, ndigits=4)
print(round2(3.14159)) # 3.14
print(round4(3.14159)) # 3.1416
partialmethod — For Class Methods¶
partialmethod works like partial but is designed for use inside class definitions:
from functools import partialmethod
class Connection:
def __init__(self):
self._connected = False
def set_state(self, state):
self._connected = state
print(f"Connected: {self._connected}")
connect = partialmethod(set_state, True)
disconnect = partialmethod(set_state, False)
conn = Connection()
conn.connect() # Connected: True
conn.disconnect() # Connected: False
Why Not Use Regular partial¶
class Broken:
def set_state(self, state):
self._state = state
# This doesn't work — partial doesn't know about 'self'
# connect = partial(set_state, True) # TypeError!
# partialmethod handles the descriptor protocol correctly
connect = partialmethod(set_state, True) # Works!
Chaining partial¶
from functools import partial
def request(method, url, headers=None, timeout=30, retries=1):
return f"{method} {url} (timeout={timeout}, retries={retries})"
# Build up configuration in layers
authenticated = partial(request, headers={"Auth": "token"})
fast_auth = partial(authenticated, timeout=5)
resilient_fast_auth = partial(fast_auth, retries=3)
print(resilient_fast_auth("GET", "/api/data"))
# GET /api/data (timeout=5, retries=3)
Note: Later keyword arguments override earlier ones:
from functools import partial
def func(a=1, b=2, c=3):
return (a, b, c)
p1 = partial(func, a=10, b=20)
p2 = partial(p1, b=99) # Overrides b=20
print(p2()) # (10, 99, 3)
Use with map, filter, sorted¶
from functools import partial
# With map
numbers = [1, 2, 3, 4, 5]
multiply_by_3 = partial(lambda x, factor: x * factor, factor=3)
result = list(map(multiply_by_3, numbers))
print(result) # [3, 6, 9, 12, 15]
# Simpler with operator
from operator import mul
triple = partial(mul, 3)
result = list(map(triple, numbers))
print(result) # [3, 6, 9, 12, 15]
# With filter
is_greater_than_3 = partial(lambda threshold, x: x > threshold, 3)
result = list(filter(is_greater_than_3, numbers))
print(result) # [4, 5]
# With sorted
data = ["banana", "apple", "cherry"]
sort_by_length = partial(sorted, key=len)
print(sort_by_length(data)) # ['apple', 'banana', 'cherry']
Common Pitfalls¶
Positional Argument Order¶
from functools import partial
def divide(a, b):
return a / b
# This fixes 'a', not 'b'
half = partial(divide, 2) # divide(2, x) → 2/x, not x/2!
print(half(10)) # 0.2 (not 5.0!)
# To fix 'b', use keyword argument
half = partial(divide, b=2) # divide(x, b=2) → x/2
print(half(10)) # 5.0
Keyword Override Silently Replaces¶
from functools import partial
def func(a, b, c):
return (a, b, c)
p = partial(func, b=10)
print(p(1, c=3)) # (1, 10, 3)
print(p(1, b=99, c=3)) # (1, 99, 3) — b=10 silently overridden
Summary¶
| Feature | Details |
|---|---|
| Import | from functools import partial |
| Purpose | Freeze arguments to create specialized functions |
| Attributes | .func, .args, .keywords |
| Class variant | partialmethod for methods in class bodies |
| Picklable | Yes (unlike lambda) |
Key Takeaways:
partialfreezes positional and keyword arguments, creating a simpler callable- Prefer
partialoverlambdafor simple argument freezing — it's more readable and inspectable - Use
partialmethodinside class definitions whereselfis involved - Watch positional argument order —
partial(f, x)always fills the first positional argument - Later keyword arguments override earlier ones when chaining partial objects
partialobjects are picklable, making them suitable for multiprocessing