A callback is a function that you pass to another piece of code with the understanding that it will be called later, when a specific condition is met or a specific event occurs. The code that receives the callback does not know what the callback does internally. It only knows when to invoke it and what arguments to pass. This inversion of control, where the caller defines the behavior but the receiver decides when to trigger it, is the essence of the callback pattern and one of the most practical applications of Python's first-class functions.
The article on higher-order functions in Python covered functions that accept other functions as arguments. Callbacks are a specialized case of this pattern where the timing of the call is the key aspect. When you pass len to sorted as the key argument, sorted calls len immediately during the sorting process, and the call happens before sorted returns. When you pass a callback to a function that starts a network request, the callback might not be called for several seconds, and it might be called from a different thread. The temporal separation between passing the callback and having it invoked is what distinguishes callbacks from other function arguments.
Callbacks for customizing behavior
The simplest callback pattern is providing a function that customizes how another function operates. Sorted with a key argument is a callback, though the term is more often reserved for cases where the callback is the primary mechanism of customization. A function that processes a list of files might accept a callback that is called for each file, letting the caller decide whether to count it, analyze it, or skip it:
def process_files(filenames, callback):
results = []
for filename in filenames:
result = callback(filename)
if result is not None:
results.append(result)
return results
def extract_extension(filename):
return filename.split(".")[-1] if "." in filename else NoneThe process_files function iterates through the filenames and calls the callback parameter on each one, collecting the non-None results into a list. The caller passes a function like extract_extension to get file extensions, or a different callback to get file sizes or filtered results. The iteration logic is written once in the higher-order function, and the per-file behavior is pluggable through the callback, a pattern also covered in the article on writing reusable Python functions.
This pattern appears throughout Python's standard library and third-party packages. Any function that traverses a data structure, iterates over a collection, or handles a stream of events can benefit from accepting a callback that lets the caller inject behavior at each step without modifying the traversal or iteration code.
Callbacks for event handling
Event-driven programming relies on callbacks as its core mechanism. A graphical user interface framework registers callback functions for events like button clicks, key presses, and window resizes. The framework's event loop waits for events, and when one occurs, it looks up the registered callback and calls it with information about the event. The callback does not need to know how the event loop works; it only needs to handle the specific event it was registered for:
class Button:
def __init__(self):
self._on_click = None
def on_click(self, callback):
self._on_click = callback
def click(self):
if self._on_click:
self._on_click()A Button instance stores a callback in the attribute _on_click. When the click method is called, perhaps by a GUI framework's event loop, the stored callback is invoked. The Button does not know what the callback does; it only knows to call it when clicked. A caller registers a handler with button.on_click(handle_click), where handle_click is a function like def handle_click(): print("Clicked!"), and that handler fires whenever button.click() runs.
The Button does not know or care what handle_click does. It only knows that a callback was registered and should be called when the button is clicked. This separation of concerns is what makes event-driven systems composable: the button handles input detection, and the callback handles the application's response.
Callbacks for asynchronous operations
Before Python's async and await syntax became widely available, callbacks were the primary mechanism for handling asynchronous results. A function that starts a long-running operation, like reading a file or making an HTTP request, accepts a callback to call when the operation completes:
def fetch_data(url, on_success, on_error):
try:
response = perform_request(url)
on_success(response)
except Exception as e:
on_error(e)Two callbacks are provided: one for success and one for failure. The fetch_data function decides which to call based on the outcome of the request.
In modern Python, async and await have largely replaced explicit callbacks for asynchronous code because they produce code that reads sequentially rather than being split across callback functions. The article on asynchronous programming later in this learning path covers this evolution in detail. Callbacks remain the underlying mechanism that many async frameworks use internally, and understanding callbacks is still necessary for working with libraries and systems that expose callback-based APIs.
Designing functions that accept callbacks
When you write a function that accepts a callback, document three things clearly. First, when will the callback be called? Is it called synchronously before your function returns, or asynchronously at some later time? Second, what arguments will the callback receive? The callback's parameter list must match what your function provides. Third, what should the callback return, and what will your function do with that return value? If the return value is ignored, say so. If None has a special meaning, document it.
Make the callback optional when reasonable by providing a default of None and checking for it before calling:
def process_items(items, on_each=None):
results = []
for item in items:
if on_each:
result = on_each(item)
results.append(result)
return resultsA function that accepts an optional progress callback lets callers that want progress updates provide one while callers that do not care can omit it. The default behavior should be to skip the callback entirely rather than to call a no-op function, because checking for None is simpler and clearer than defining and passing a do-nothing lambda.
Rune AI
Key Insights
- A callback is a function passed as an argument to be called later, typically when an event occurs or a task completes.
- Callbacks rely on Python's first-class functions and are a specific application of higher-order functions.
- The sorted key parameter, GUI event handlers, and asynchronous completion handlers are all callback patterns.
- In modern Python, async and await often replace explicit callbacks for asynchronous code.
- When writing a function that accepts a callback, document when and with what arguments the callback will be invoked.
Frequently Asked Questions
What is a callback function in Python?
How is a callback different from a regular function argument?
What are the alternatives to callbacks in modern Python?
Conclusion
Callbacks are the mechanism by which one piece of code says to another: here is a function, call it when you are ready. They are the natural extension of first-class functions and higher-order functions into the domain of event-driven and asynchronous programming. Understanding callbacks prepares you for both the traditional callback-based APIs you will encounter in Python libraries and the async and await patterns that build on the same conceptual foundation.
More in this topic
Define and Call Python Functions
Learn the exact syntax for defining and calling Python functions with the def keyword, including naming rules, the function body, and how the call stack works.
Python Function Parameters and Arguments
Learn the difference between parameters and arguments, how to pass values by position and by keyword, and the rules for default parameter values.
Python Functions Explained
Learn what Python functions are, why they matter for organizing code, and how they form the foundation of non-trivial programs.