Python Basics: Preconfigure Calls with partial and partialmethod
Several callbacks may perform the same operation with different options. functools.partial creates a callable with some arguments already supplied, leaving the remaining inputs for the caller. This can keep a small adapter clear without introducing another function definition.
The examples below format measurements and collect invented messages in memory. They do not send messages, configure logging, or cache results.
Prepare a scratch directory
These examples were tested on Linux with Python 3.12.14. These examples target Python 3.6 or newer. Use Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. No third-party packages are required. Run without -O so the assertions remain enabled.
Create a fresh directory and keep using the same terminal:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1If either setup command fails, stop before saving any files. Save each named source below as UTF-8. Avoid names such as functools.py that shadow standard-library modules.
1. Supply defaults for a one-input callback
Save as configure_measurement_format.py:
from functools import partial
def format_measurement(value, *, unit, places):
return f"{value:.{places}f} {unit}"
as_milliseconds = partial(format_measurement, unit="ms", places=1)
labels = list(map(as_milliseconds, [2.25, 8.5]))
assert labels == ["2.2 ms", "8.5 ms"]
assert as_milliseconds(2.25, places=2) == "2.25 ms"
assert as_milliseconds.func is format_measurement
assert as_milliseconds.keywords == {"unit": "ms", "places": 1}
print("Labels:", labels)
print("Override:", as_milliseconds(2.25, places=2))
print("Saved places:", as_milliseconds.keywords["places"])Run it:
python3 -B configure_measurement_format.pyOutput:
Labels: ['2.2 ms', '8.5 ms']
Override: 2.25 ms
Saved places: 1map supplies only the measurement. The partial supplies the other options. A keyword passed at call time overrides the stored keyword for that call, without changing the saved default. partial adapts the call; the formatting function still runs every time.
Here 2.25 is exactly representable, and one decimal place uses Python’s usual tie-to-even formatting. Formatting arbitrary floating-point values is a display decision, not a substitute for a decimal-arithmetic policy.
2. Notice which objects a callback keeps
Save as retain_callback_arguments.py:
from functools import partial
def record_event(events, prefix, message):
events.append(f"{prefix}: {message}")
return len(events)
events = []
record_job = partial(record_event, events, "job")
assert events == []
assert record_job.args[0] is events
assert record_job("started") == 1
events.append("manual note")
assert record_job("finished") == 3
try:
record_job("ignored", prefix="other")
except TypeError:
print("Rejected: prefix was supplied twice")
else:
raise AssertionError("Conflicting arguments were accepted")
assert len(events) == 3
print("Shared list:", record_job.args[0] is events)
print("Events:", events)Run it:
python3 -B retain_callback_arguments.pyOutput:
Rejected: prefix was supplied twice
Shared list: True
Events: ['job: started', 'manual note', 'job: finished']Constructing the partial does not call record_event. Its stored positional arguments are prepended later, and the list is retained by reference. A keyword cannot replace a parameter already filled positionally: that gives two values for prefix. The partial object attributes make these bindings inspectable.
This fixture intentionally shares a list. In an application, decide who owns mutable callback state and how long the callback should keep it alive. Creating a partial does not snapshot that state or synchronize access from multiple threads.
3. Define convenient instance methods
Save as configure_journal_methods.py:
from functools import partialmethod
class Journal:
def __init__(self, name):
self.name = name
self.entries = []
def write(self, level, text):
entry = f"{self.name} {level}: {text}"
self.entries.append(entry)
return entry
info = partialmethod(write, "INFO")
warning = partialmethod(write, "WARNING")
build = Journal("build")
preview = Journal("preview")
print(build.info("ready"))
print(preview.warning("missing image"))
assert build.entries == ["build INFO: ready"]
assert preview.entries == ["preview WARNING: missing image"]
assert build.entries is not preview.entries
print("Separate journals:", build.entries is not preview.entries)Run it:
python3 -B configure_journal_methods.pyOutput:
build INFO: ready
preview WARNING: missing image
Separate journals: Truepartialmethod is intended for definitions inside a class. Access through an instance binds self before the saved level and the new text. An ordinary partial stored on the class does not provide the same method-binding behavior. See partialmethod.
Use a normal named function when the adapter needs validation, branching, or a substantial explanation. A partial is most readable when its saved arguments make the remaining call obvious. It also does not automatically acquire the wrapped function’s name and docstring.
Clean up
In the same terminal, remove only these sources and the empty scratch directory:
rm -- configure_measurement_format.py retain_callback_arguments.py configure_journal_methods.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirIf rmdir reports remaining files, inspect them instead of deleting unknown contents.
References
Acknowledgements
This post was created with assistance from AI.