Configure Callbacks with functools.partial
Build a callable with selected arguments fixed while keeping other arguments open.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: Configure Callbacks with functools.partial
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
A callback is a function you hand to another part of a program to call later. Often the callback needs a little configuration: a multiplier, a log level, or a destination. One tempting way to create several callbacks is a lambda inside a loop. But the lambda may look up the loop variable when it runs, after the loop has finished. Then every callback sees the final value. Python's `functools.partial` provides a direct way to bind some arguments now and supply the remaining arguments later. We will reproduce the loop bug, fix it with `partial`, and build a small event formatter that uses a preconfigured level.
- 00:40
No user-interface framework is required; a list of functions is enough to demonstrate deferred calls. Pay attention to when each value is chosen. The function passed to `partial` remains the original callable, and the new callable remembers selected arguments. This is a useful pattern for adapters and callbacks, but it should not hide complex state or validation. We will test the behavior with more than one input. The callbacks are created while `factor` takes the values two, three, and four. They are invoked only after the loop ends, when the variable refers to four. Each lambda then multiplies five by four, producing twenty three times.
- 01:22
The code did not store three separate factor values inside the lambdas. This behavior is called late binding of the captured name. A common fix is to use a default argument in the lambda, such as `lambda value, factor=factor: ...`, which captures the current value. That is valid, but it can be easy to miss in a larger expression. If the operation already exists as a named function, `partial` makes the configuration step explicit. We will bind each factor when building the list. The key test is to invoke the callbacks later, not inside the loop, because invoking them immediately can conceal this exact bug.
- 02:03
Different input values should still work once the factor is captured correctly. Each call to `partial` creates a new callable with its own bound `factor`. When we later supply `value=5`, the three functions return ten, fifteen, and twenty. The first callback still accepts another value, so seven becomes fourteen. This is partial application: provide some arguments now and the rest at call time. We bound `factor` by keyword because it says what the number means. A partial can also bind leading positional arguments, which is useful for a callback such as a warning logger where the level should always be `WARN`.
- 02:43
The original `scale` function has not changed; it still accepts both arguments. `partial` packages a function together with the choices we have made for one use. Do not assume a partial validates values. If a factor must be positive or a level must be from an allowed set, the underlying function or the creation step should enforce that rule. Next we will use this pattern to configure a readable event message. `warn` is a callable configured with the first argument of `format_event`. The caller supplies only the message. That makes it easy to pass to another function that expects a one-argument callback.
- 03:23
Two different messages show that the level is fixed while the message remains variable. The original `format_event` still accepts an `INFO` level because `partial` creates a wrapper; it does not mutate the function definition. The validation belongs in the underlying function, so every path through it checks the level. In an application, you might pass `warn` to a queue consumer, a parser, or a UI event handler. The receiver does not have to know about the level argument at all. Be careful with mutable bound arguments such as lists or dictionaries: a partial holds references to objects, so later mutation can affect calls.
- 04:03
Here the bound string is immutable and simple. The lesson page includes a task where you configure a callback and then invoke it with several inputs to show what is fixed and what remains open. Two separate partials keep their factors independent. The assertions use positive, zero, and negative inputs so the test does more than repeat one sample. The configured warning callback includes the fixed level and the supplied message. An unsupported level still raises an error through the general function. This shows `partial` did not bypass validation. Try replacing the two partials with lambdas built in a loop and invoking them
- 04:44
afterward; you should be able to predict the repeated factor from the broken run. Then try binding a factor positionally versus by keyword and inspect which arguments remain available. `partial` is useful when a function's behavior should be configured before it is passed somewhere else. It does not make hidden global state disappear by itself; the clarity comes from explicitly recording selected arguments at creation time and testing calls later. Complete the practice task by producing callbacks with distinct retained settings and explaining when each argument is chosen.
Understand the concept
A callback is a function that another part of a program calls later. A common bug is passing make_label('ERROR', message) where a callback is expected. That expression runs immediately and passes its result, usually a string, rather than a callable. functools.partial constructs a new callable by fixing some arguments in advance. The remaining arguments arrive when the callback is invoked.
Consider a formatter format_event(level, message, *, prefix='APP'). The level can be fixed to ERROR while the message remains open. partial(format_event, 'ERROR', prefix='WEB') returns a callable. Calling it with 'timeout' produces 'WEB ERROR: timeout'. This is easier to inspect than a lambda when all we need is to bind parameters, though a lambda is appropriate for more complex transformations.
Partial does not validate the original function's arguments at creation time. An invalid keyword or missing required parameter may fail only when the new callable runs. Test the configured callback with representative input and check its return value. Passing a mutable object as a bound argument can also share state unexpectedly across calls, so use immutable configuration values for this introductory example.
The checked make_logger factory validates a permitted level and nonblank prefix, then returns a partially applied format_event callable. It must not print or call the formatter during construction. Tests invoke the result twice with different messages, confirm the value is callable, and reject unknown levels and prefixes. This pattern also works for sorting keys, event handlers, and small dependency configurations.
- Callbacks are called later
- partial binds positional and keyword arguments
- Unbound arguments remain open
- Construction must not trigger the callback
See it step by step
Read the code, predict the output, then compare it with the result.
01. Reveal the early-call mistake
def shout(text):
return text.upper()
callback = shout('ready')
print(type(callback).__name__, callback)str READYCalling a function here produces a string, not a callback for later use.
02. Bind one argument and call later
from functools import partial
def label(level, message):
return f'{level}: {message}'
error_label = partial(label, 'ERROR')
print(callable(error_label))
print(error_label('timeout'))True
ERROR: timeoutPartial stores the level while leaving the message open.
03. Bind a keyword without changing the function
from functools import partial
def label(level, message, *, prefix='APP'):
return f'{prefix} {level}: {message}'
web_error = partial(label, 'ERROR', prefix='WEB')
print(web_error('timeout'))
print(label('INFO', 'ready'))WEB ERROR: timeout
APP INFO: readyThe original function still uses its normal default when called directly.
A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Recognize an accidental early call
A callback must remain callable until the receiving code decides to invoke it. In the first example, shout('ready') runs immediately and assigns its return value to callback. Printing the type reveals str, and the stored value is READY. Passing that string to a function expecting a callback would fail later and obscure the original mistake.
Use this diagnostic when wiring event handlers or configurable formatters: inspect callable(candidate) before registration, and pay attention to parentheses. The goal is to keep the moment of invocation deliberate. When arguments must be supplied now but execution must wait, build a new callable instead of storing a computed result.
def shout(text):
return text.upper()
callback = shout('ready')
print(type(callback).__name__, callback)str READY- Call the function too early on purpose.
- Inspect the resulting type and value.
- Keep the desired callback callable for later invocation.
Freeze one argument with partial
The second run binds the level ERROR to a two-argument label function. partial returns a callable that still expects the message. callable(error_label) prints True; calling it with timeout then produces ERROR: timeout. This separates configuration time from event time without introducing a new wrapper function for one simple binding.
Use a partially applied function when the transformation is just argument binding. A lambda can be clearer when additional logic is required, but a plain partial exposes the underlying function and stored arguments directly. Test the configured callback with at least two messages so the open parameter really remains open.
from functools import partial
def label(level, message):
return f'{level}: {message}'
error_label = partial(label, 'ERROR')
print(callable(error_label))
print(error_label('timeout'))True
ERROR: timeout- Pass the original function to partial without invoking it.
- Bind only the level argument.
- Supply each message when the returned callable runs.
Bind a keyword and preserve the original default
The last example fixes a keyword-only prefix for one callback and leaves the original function untouched. The configured web callback prints WEB ERROR: timeout, while a direct call still uses APP INFO: ready. A partial is a new callable; it does not edit the definition or default arguments of the function it wraps.
Validate bound configuration before returning a callback when the application has a set of allowed values. partial does not run the underlying function at construction, so some argument mistakes surface only later. Prefer immutable bound values for this introductory pattern, since binding a mutable object can accidentally share state across calls.
from functools import partial
def label(level, message, *, prefix='APP'):
return f'{prefix} {level}: {message}'
web_error = partial(label, 'ERROR', prefix='WEB')
print(web_error('timeout'))
print(label('INFO', 'ready'))WEB ERROR: timeout
APP INFO: ready- Bind positional and keyword arguments deliberately.
- Call both the configured and original functions.
- Validate level and prefix before exposing a callback to callers.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Validate both settings, then return functools.partial(format_event, level, prefix=prefix). Do not supply the message yet.
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Calling during registration
callback = format_event('ERROR', 'ready')callback = partial(format_event, 'ERROR')What happens: callback is a string and cannot be invoked later.
Pass a configured callable, then supply the message later.
Binding a mutable shared default
callback = partial(add_item, items=[])callback = partial(add_item, items=tuple())What happens: Later calls unexpectedly share and mutate one list.
Prefer immutable bound configuration or create fresh state per call.