The functools.partial module in Python is a simple but incredibly handy tool for working with functions. By "partially applying" arguments to a function, partial allows us to simplify functions, specialize them, preset defaults, and more. In this guide, we‘ll unlock the various uses of partial and how it can make working with Python functions more convenient.
What is functools.partial?
The partial function from Python‘s functools module creates a new function by "freezing" some portion of another function‘s arguments and keywords. The resulting "partial function" has fewer parameters than the original function.
For example:
from functools import partial
def add(x, y):
return x + y
add_five = partial(add, y=5)
print(add_five(10)) # Outputs 15
Here partial was used to bind the y parameter of add() to the value 5. This created a new function add_five() with only one parameter x.
The key benefit is simplifying functions by fixing arguments. The parameters captured by partial() can be positional, keywords, or both. We essentially specialize the function by presetting values.
How functools.partial() Works
Under the hood, partial() copies over the original function‘s __name__, __doc__, and other metadata. A partial instance is callable and behaves similarly to the original function. But some arguments are already "filled in" when called.
The function signature is not actually changed – the pre-bound parameters are just appended to the new arguments when called. Any extra arguments supplied to a partial function are evaluated first.
For example:
from functools import partial
def add(x, y):
return x + y
add_five = partial(add, 5)
print(add_five(10)) # 15
print(add_five(10, 20)) # 25, y gets overwritten by 20
One tricky note is that positional arguments cannot follow preset keyword arguments. For example:
my_func = partial(func, y=5, x=10) # Works
my_func = partial(func, x=10, y=5) # Raises error
So the calling order stays consistent with the original function.
Basic Usage Examples
Let‘s look at a few more examples of creating and using partial functions:
Freezing select arguments
Fix some args while leaving others variable:
from functools import partial
def multiply(x, y):
return x * y
dbl = partial(multiply, 2) # Always double the input
print(dbl(4)) # Prints 8
Presetting default values
Set defaults that simplify function calls:
import math
from functools import partial
def calc(num, power=2, root=False):
if root:
return math.sqrt(num)
return math.pow(num, power)
square = partial(calc, power=2)
sqrt = partial(calc, root=True)
print(square(5)) # 25
print(sqrt(4)) # 2.0
Binding keywords
Bind specific keywords to preset values:
from functools import partial
def connect(host, port, timeout):
print(f"Connecting to {host}:{port} (timeout: {timeout})")
connect_dev = partial(connect, host="localhost", port=5000)
connect_dev(timeout=10)
# Connecting to localhost:5000 (timeout: 10)
This lets us customize the function more cleanly than positional args.
Comparing to lambdas
partial gives more descriptive function names than lambdas:
from functools import partial
square = partial(pow, 2)
square(5)
# vs
square = lambda x: pow(x, 2)
square(5)
So partial helps simplify functions while improving readability.
Specializing and Adapting Functions
Let‘s look at some more advanced ways to adapt and specialize functions using partial.
Customizing for specific uses
We can tweak functions for more specific uses:
import json
from functools import partial
# Dump JSON with 4-space indent
dump = partial(json.dump, indent=4)
data = {...}
with open(‘data.json‘, ‘w‘) as f:
dump(data, f) # Automatically indented
Currying functions
"Currying" refers to converting a function that takes multiple arguments into a sequence of functions that take part of the arguments.
We can curry functions with partial:
from functools import partial
def add(x, y, z):
return x + y + z
add_part = partial(add, 1, 2)
add_part(10) # 13
This technqiue is useful for mathematical functions or combinatorics.
Simplifying APIs
partial is handy for simplifying functions from external APIs:
from functools import partial
import tensorflow as tf
# Simplify creating L2 regularizers
l2_regularizer = partial(tf.keras.regularizers.L2, l2=0.01)
model.add(Dense(64, activation=‘relu‘,
kernel_regularizer=l2_regularizer()))
We avoid retyping the same arguments each time.
Renaming functions
Giving more descriptive names can improve readability:
from functools import partial
import numpy as np
normalize = partial(np.linalg.norm, ord=2)
v = [1, 2, 3]
normalize(v) # 3.7416...
Binding methods
Class instance methods can be bound to self with partial:
from functools import partial
class Renderer:
# ...
def render(self, scene):
print(f"Rendering {scene}")
r = Renderer()
render_scene = partial(r.render, scene="Demo")
render_scene() # Rendering Demo
This can be useful in many cases where we want to customize a method.
Use Cases
Let‘s explore some common use cases where partial can simplify coding.
Callback Functions
partial is handy for configuring callback functions with preset arguments:
from functools import partial
def handle_event(event, name, severity, log):
log(f"{name}: {event} ({severity})")
warning_logger = partial(handle_event, severity="WARNING")
warning_logger("Disk full", "Storage", log=print)
# Prints "Storage: Disk full (WARNING)"
Specializing Math Functions
We can create shortcuts to general math functions for specific cases:
from functools import partial
from math import sqrt, log2
sqrt3 = partial(sqrt, 3)
log2_4 = partial(log2, 4)
print(sqrt3()) # 1.732050...
print(log2_4()) # 2
Parameter Sets
For functions we call with the same parameter combinations, we can define:
from functools import partial
import request
get = partial(requests.get, timeout=10, headers={...})
post = partial(requests.post, timeout=10, headers={...})
resp = get(‘https://example.com/api/data‘)
resp = post(‘https://example.com/api/update‘, json={...})
This removes repetitive arguments.
Concurrency
Since partial returns a Callable, we can prep functions to easily spawn with multiprocessing or threads:
from functools import partial
from concurrent.futures import ProcessPoolExecutor
def work(id, data):
print(f"Working on {id}!")
# Process data
with ProcessPoolExecutor(max_workers=4) as pool:
futures = [pool.submit(work, id, data)
for id, data in enumerate(items)]
The partial call prepares the concurrency while abstracting way args.
Performance Considerations
partial functions have minor overhead from wrapping the original function. An additional function call happens each time it is invoked.
Generally this overhead is negligible, especially for I/O bound operations. But it can become noticeable for very frequent calls or performance sensitive code.
It‘s best to avoid unnecessary use of partial in hot loops or sections of code where speed is critical. Profile code to identify any bottlenecks from partial overhead.
In some cases, a lambda function may perform better than partial. But partial helps organize larger programs.
Conclusion
Python‘s functools.partial provides a simple but immensely helpful tool for working with functions. By binding arguments to functions, partial allows us to adapt behavior, preset defaults, rename, customize, and even curry functions.
While Python has other techniques like lambda and decorators, partial strikes a unique balance between readability and flexibility. It helps reduce code repetition and allows cleaner, more Pythonic function usage.
By mastering the basics of partial, you can write large Python programs that are easier to understand, adapt, and maintain.




