I discovered functools.partial while trying to clean up a mess of small wrapper functions I’d written just to “pre-fill” a few arguments before passing a function somewhere else — usually as a callback. I had things like def add_five(x): return add(5, x) scattered everywhere. Once I learned about partial functions, half of those wrapper functions disappeared overnight. This guide covers everything I now know about partial functions: what they are, how they work internally, and where they genuinely make code better instead of just “clever.”
What a Partial Function Is
A partial function is a new function created by fixing some of the arguments of an existing function, leaving the rest to be supplied later. Python’s standard library gives us this directly through functools.partial.
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(4)) # 16
print(cube(2)) # 8
Here, square and cube are new callables that already “remember” part of the call — I don’t need to pass exponent again.
How Partial Functions Work Internally
functools.partial is implemented as a class (in CPython, it also has a C-accelerated implementation for speed), and creating a partial object stores three things: the original function, a tuple of pre-filled positional arguments, and a dictionary of pre-filled keyword arguments.
p = partial(power, 2)
print(p.func) # <function power>
print(p.args) # (2,)
print(p.keywords) # {}
When you later call p(3), internally it’s roughly equivalent to:
p.func(*(p.args + (3,)), **p.keywords)
That is, new positional arguments are appended after the stored ones, and new keyword arguments update (and can override) the stored keyword dictionary. I confirmed this by digging into CPython’s functools.py source — the pure-Python fallback implementation makes the mechanism completely transparent:
class partial:
def __new__(cls, func, /, *args, **keywords):
...
def __call__(self, /, *args, **keywords):
newkeywords = {**self.keywords, **keywords}
return self.func(*self.args, *args, **newkeywords)
Positional vs Keyword Pre-Filling
Positional pre-filled arguments always apply to the leftmost parameters, in order, and you cannot skip over them. Keyword pre-filled arguments are more flexible since they bind by name.
def greet(greeting, name, punctuation="!"):
return f"{greeting}, {name}{punctuation}"
hello = partial(greet, "Hello")
print(hello("Ali")) # Hello, Ali!
polite = partial(greet, punctuation=".")
print(polite("Hi", "Sara")) # Hi, Sara.
I generally prefer pre-filling by keyword whenever the function has more than two parameters, since it makes the intent obvious at the call site and avoids positional ordering mistakes.
partialmethod: Partial Functions for Class Methods
A less commonly known sibling is functools.partialmethod, which behaves like partial but is designed to work correctly with instance methods (it properly handles the implicit self argument through the descriptor protocol):
from functools import partialmethod
class Formatter:
def format(self, value, prefix=""):
return f"{prefix}{value}"
bold = partialmethod(format, prefix="**")
italic = partialmethod(format, prefix="_")
f = Formatter()
print(f.bold("Important")) # **Important
print(f.italic("Note")) # _Note
This trick saved me from writing three nearly identical methods in a text-formatting utility class I built for a reporting tool.
Real-World Use Cases
1. GUI and event callbacks. Libraries like Tkinter often expect a zero-argument callable for a button’s command. Partial functions let me pre-bind arguments cleanly:
import tkinter as tk
from functools import partial
def on_click(label, value):
print(f"{label} clicked with value {value}")
root = tk.Tk()
btn = tk.Button(root, text="Click", command=partial(on_click, "Submit", 42))
2. Configuring library functions. I frequently specialize print or logging calls:
import sys
error_print = partial(print, file=sys.stderr)
error_print("Something went wrong")
3. Simplifying map() and sorted() calls.
numbers = ["3", "1", "4", "1", "5"]
to_int = partial(int, base=10)
print(list(map(to_int, numbers)))
4. Building pipelines of specialized functions. In one data-cleaning script, I used partials to create a set of specialized validators from a single generic validator function, each pre-configured with different thresholds.
def in_range(value, low, high):
return low <= value <= high
is_valid_age = partial(in_range, low=0, high=120)
is_valid_percentage = partial(in_range, low=0, high=100)
print(is_valid_age(45)) # True
print(is_valid_percentage(150)) # False
Partial Functions vs Lambdas vs Closures
I get asked often why not just use a lambda instead:
square_lambda = lambda x: power(x, 2)
square_partial = partial(power, exponent=2)
Both work, but there are real differences I’ve come to appreciate:
partialobjects carry introspectable metadata (.func,.args,.keywords), which makes debugging and testing easier — you can literally inspect what was pre-bound.partialis generally slightly faster than an equivalent lambda for simple pre-binding, since it avoids the overhead of a fresh Python-level function call wrapping another call.- Lambdas are more flexible when you need actual logic (conditionals, multiple statements via helper calls) rather than pure argument binding.
partialobjects pickle more reliably than lambdas in multiprocessing contexts, since lambdas cannot be pickled at all by the standardpicklemodule, whilepartialobjects can be pickled if the underlying function and arguments are picklable.
That last point matters a lot in practice — I ran into this directly when using multiprocessing.Pool.map(), where passing a lambda raises a PicklingError, but a partial object works fine as long as the wrapped function is defined at module level.
from multiprocessing import Pool
from functools import partial
def multiply(x, factor):
return x * factor
if __name__ == "__main__":
triple = partial(multiply, factor=3)
with Pool(4) as pool:
print(pool.map(triple, [1, 2, 3, 4]))
Performance Characteristics
Creating a partial object is a cheap, constant-time operation — it just stores references, it doesn’t copy the function or evaluate anything. Calling a partial object involves one extra layer of indirection compared to calling the original function directly, similar in cost to a decorator wrapper call. In CPython, functools.partial has a C implementation (_functools.partial) that’s noticeably faster than an equivalent hand-written Python wrapper function, which is one reason I prefer it over manually writing small wrapper functions for argument binding.
Best Practices and Common Mistakes
- Prefer keyword arguments when pre-filling to avoid positional ordering confusion, especially for functions with more than two or three parameters.
- Remember that positional args supplied to a partial object are appended after the ones already stored — you can’t insert a new positional argument before a pre-filled one.
- Don’t overuse partials to the point where code becomes hard to trace; if a partial chain gets too deep, a regular named function is often clearer.
- Use
partialmethodinstead ofpartialfor class attributes meant to work as instance methods — using plainpartialthere won’t handleselfcorrectly. - When passing partials to
multiprocessing, ensure the underlying function is defined at module level (not a local/nested function), or pickling will fail.
Troubleshooting Tips
If you get TypeError: power() missing 1 required positional argument, check whether you pre-filled the wrong argument or forgot to supply the remaining one at call time.
If a partial silently overrides an argument you didn’t expect, remember new keyword arguments passed at call time override any pre-filled keyword arguments with the same name.
If pickling a partial fails inside multiprocessing, verify the wrapped function isn’t a lambda or a locally defined nested function.
FAQs
Is functools.partial the same as currying? It’s related but not identical — currying transforms a function into a chain of single-argument functions, while partial simply pre-binds some arguments of a multi-argument function, leaving the rest open in one final call.
Can I create a partial of a partial? Yes, partial(partial(func, a), b) works and progressively binds more arguments, though for clarity I usually just pass multiple arguments to a single partial call.
Does partial work with built-in functions? Yes, as shown with int and print above — any callable works, including built-ins, methods, and other partial objects.
Can partial objects have their bound arguments changed after creation? No, partial objects are immutable regarding .args and .keywords — attempting to modify them directly isn’t supported; create a new partial instead.
Summary
functools.partial lets me specialize existing functions by pre-filling some of their arguments, producing new, reusable callables without writing throwaway wrapper functions. Internally it’s a thin, efficient object that stores the original function plus fixed positional and keyword arguments, applying them at call time. I reach for it constantly in callback-heavy code, functional-style pipelines with map()/filter(), and multiprocessing tasks where lambdas aren’t an option. Once you start looking for repeated “wrapper functions that just fix one argument,” you’ll find partial functions everywhere in your own code too.
