Python functools.partial creates a new callable from an existing function by fixing some arguments in advance. The feature is small, but it is valuable when you need to adapt callbacks, reduce repeated configuration, prepare a function for an API with a specific signature, or make processing pipelines easier to read.
This guide explains how partial() works, when it improves code, which mistakes to avoid, and how to combine it with named functions, methods, callbacks, type hints, and tests. It complements our guides to Python functions, lambda functions, decorators, args and kwargs, and type hints.
What functools.partial does
partial() receives a function plus positional or keyword arguments. It returns a callable that invokes the original function with those values already supplied.
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
print(square(5)) # 25The original function remains unchanged. The square object remembers that exponent should be 2. Calling square(5) is equivalent to calling power(5, exponent=2).
Why fix arguments in advance
Suppose an application converts many values using the same exchange rate:
def convert(value, rate, rounded=True):
result = value * rate
return round(result, 2) if rounded else result
convert_to_local = partial(convert, rate=5.42)
print(convert_to_local(10))
print(convert_to_local(25))Without partial, you would repeat rate=5.42 everywhere or write a manual wrapper. The partial version makes the specialized operation explicit.
Positional arguments
Positional arguments stored in a partial are inserted before arguments passed later.
def surround(prefix, text, suffix):
return f"{prefix}{text}{suffix}"
open_bracket = partial(surround, "[")
print(open_bracket("Python", "]"))The result is [Python]. Because order matters, positional arguments are best when the original signature is obvious and stable.
Keyword arguments
Keyword arguments are often clearer because they show the meaning of each fixed value.
import json
from functools import partial
pretty_json = partial(
json.dumps,
ensure_ascii=False,
indent=2,
sort_keys=True,
)
print(pretty_json({"course": "Python", "active": True}))This pattern creates a configured serializer once. The official functools.partial documentation describes how stored arguments are merged with later calls.
Overriding stored keywords
A keyword stored in the partial can be replaced when the callable is invoked:
json_output = partial(json.dumps, ensure_ascii=False, indent=2)
print(json_output({"a": 1}, indent=None))The later value wins. This flexibility is useful, but if most calls override the stored configuration, the partial may not represent a meaningful specialization.
Adapting callbacks
Many libraries call callbacks with a fixed signature. A partial can attach context without global variables.
def log_event(category, message):
print(f"[{category}] {message}")
log_error = partial(log_event, "ERROR")
log_info = partial(log_event, "INFO")
log_error("File not found")
log_info("Processing complete")This works well in desktop interfaces, queues, schedulers, event systems, and web frameworks. The callback remains small, while the context is visible where it is created.
Using partial with map
def discount(value, percentage):
return value * (1 - percentage)
ten_percent = partial(discount, percentage=0.10)
prices = [100, 250, 80]
result = list(map(ten_percent, prices))Each price is processed by the same specialized function. A list comprehension may be equally readable, so choose the form that best communicates the pipeline.
Using partial as a sorting key
def distance(target, value):
return abs(value - target)
near_ten = partial(distance, 10)
values = [1, 20, 8, 13, 5]
print(sorted(values, key=near_ten))The code clearly states that values are ordered by their distance from 10.
partial versus lambda
A lambda can solve many of the same problems:
square_lambda = lambda base: power(base, exponent=2)
square_partial = partial(power, exponent=2)Use partial when the operation is simply “call this function with these arguments pre-filled.” Use lambda when you need an expression that transforms input, combines calls, or applies a condition.
partial versus a named wrapper
A named wrapper provides its own documentation, validation, and domain language.
def square(base):
"""Return the square of a number."""
return power(base, 2)This is usually better for a public API or an important business rule. A partial is ideal for direct, local adaptation that does not require extra behavior.
Inspecting a partial object
Partial objects expose three useful attributes:
func, the original callable;args, the stored positional arguments;keywords, the stored keyword arguments.
print(square.func)
print(square.args)
print(square.keywords)These attributes help with debugging and focused tests.
Name and documentation metadata
A partial object does not automatically receive the original function’s __name__ and docstring. You can copy wrapper metadata with functools.update_wrapper():
from functools import partial, update_wrapper
square = partial(power, exponent=2)
update_wrapper(square, power)However, the copied name still identifies the original function, not the specialization. For public interfaces, a named wrapper may communicate intent more accurately.
partialmethod inside classes
Classes have a related tool called functools.partialmethod. It cooperates with the descriptor protocol, so instance binding works correctly.
from functools import partialmethod
class Publisher:
def send(self, message, level):
print(level, message)
info = partialmethod(send, level="INFO")
alert = partialmethod(send, level="ALERT")
publisher = Publisher()
publisher.info("Server started")See the official partialmethod documentation for descriptor details.
Mutable stored arguments
Arguments are stored by reference. If you bind a list or dictionary and later mutate it, future calls observe the changed object.
config = {"mode": "test"}
def run(name, config):
return name, config
configured_run = partial(run, config=config)
config["mode"] = "production"
print(configured_run("backup"))This may be intentional, but it often causes subtle bugs. Prefer immutable values or make a copy at the correct point in the workflow.
Signatures and introspection
inspect.signature() can usually produce an adapted signature for a partial:
from inspect import signature
print(signature(square))This helps IDEs and validation tools, but frameworks that depend on custom annotations or metadata may still require an explicit wrapper.
Type hints
Static type checkers can infer some partial calls, but complex signatures may become unclear. An explicit callable annotation helps:
from collections.abc import Callable
square_typed: Callable[[float], float] = partial(
power,
exponent=2,
)The annotation improves autocomplete and makes the expected contract visible.
Testing partial functions
Test observable behavior rather than only checking that an object was created.
def test_square():
assert square(4) == 16
assert square(-3) == 9If the stored configuration is part of the requirement, you can also assert values in args and keywords.
Serialization and security
A partial may be serializable when its function and arguments are serializable, but do not use that fact to load untrusted data. Pickle-based formats can execute code during deserialization. Store safe configuration data and rebuild the partial in trusted code instead.
Performance
The overhead of calling a partial is generally small and should rarely decide the design. Prefer clarity, then benchmark a real critical path with timeit when evidence is needed.
Common mistakes
- Binding positional arguments in the wrong order.
- Using a partial when a documented wrapper would be clearer.
- Storing mutable containers unintentionally.
- Forgetting that stored keywords may be overridden.
- Confusing
partialwithpartialmethod. - Assuming names and docstrings are copied automatically.
- Creating too many specialized callables and hiding the flow.
Best practices
- Choose names that describe the specialization.
- Prefer keywords for important configuration.
- Keep a partial close to the code that uses it.
- Avoid shared mutable stored arguments.
- Use named wrappers for public APIs and validation.
- Document callbacks that carry context.
- Verify the signature expected by the consuming library.
Conclusion
Python functools.partial is a focused tool for specializing functions without duplicating logic. It reduces repetitive arguments, adapts callbacks, and makes recurring configuration explicit.
Use it when the transformation is mainly about fixing arguments. When you need validation, error handling, domain documentation, or additional logic, write a normal wrapper function. With that distinction, partial improves readability without obscuring program behavior.






