Of course! The term addCallback in Python is most famously associated with Twisted, an asynchronous networking framework. However, the concept of adding a callback to be executed later is a fundamental pattern in asynchronous programming and is implemented in various ways across different libraries and even in the standard library.

Let's break it down, starting with the classic Twisted example and then exploring modern alternatives.
The Classic: addCallback in Twisted
In Twisted, you don't just call a function and get a result immediately. Instead, you initiate an operation and get a Deferred object. A Deferred is a promise that a result will be available in the future. You attach callbacks to this Deferred to handle the successful result, and errbacks to handle failures.
Key Concepts in Twisted:
Deferred: An object that represents the result of an asynchronous operation. It hasn't finished yet, but it will.- *`addCallback(callback, args, kw)`: Adds a function (
callback) to the chain of functions that will be called when theDeferredsuccessfully resolves. The result of the previous callback is passed as the first argument to the next one. - *`addErrback(errback, args, kw)`: Adds a function (
errback) to handle an error if the operation fails.
Example: A Simple HTTP Request with Twisted
This example simulates making an HTTP request and processing the response.
# You'll need to install Twisted: pip install twisted
from twisted.internet import reactor, defer
from twisted.web.client import getPage
import logging
# 1. Define your callback functions
def process_content(content):
"""This function processes the data once it's received."""
print("--- Content received! ---")
# The content is bytes, so we decode it
text_content = content.decode('utf-8')
print(f"First 100 characters: {text_content[:100]}...")
# We can return new data to be passed to the next callback
return len(text_content)
def print_content_length(length):
"""This function receives the result from the previous callback."""
print(f"--- Final Content Length: {length} characters ---")
# This callback signals that the entire chain is done.
# We can stop the Twisted reactor.
reactor.stop()
def handle_error(failure):
"""This function handles any errors that occurred."""
print("--- An error occurred! ---")
print(failure.getErrorMessage())
reactor.stop()
# 2. Create a Deferred object by calling an asynchronous function
# getPage returns a Deferred immediately.
d = getPage(b'https://www.example.com')
# 3. Add callbacks to the chain
# The result of getPage (the page content) will be passed to process_content.
d.addCallback(process_content)
# The result of process_content (the content length) will be passed to print_content_length.
d.addCallback(print_content_length)
# 4. Add an errback to handle potential failures (e.g., network error)
d.addErrback(handle_error)
# 5. Start the Twisted reactor. It will run until reactor.stop() is called.
print("Starting the Twisted reactor...")
reactor.run()
How it works:

getPage(b'https://www.example.com')is called. It doesn't wait for the response. It immediately returns aDeferredobject (d).d.addCallback(process_content)registersprocess_contentto be called when the download is successful.d.addCallback(print_content_length)registersprint_content_lengthto be called with the result ofprocess_content.- The reactor starts running in the background, handling the network request.
- When the response is received, Twisted calls the first callback in the chain (
process_content) with the response content. process_contentdoes its work and returns a value. This return value is automatically passed as an argument to the next callback in the chain (print_content_length).- If any step had raised an exception, the
addErrbackhandler would have been called instead. - Finally,
print_content_lengthcallsreactor.stop(), which shuts down the event loop and the program exits.
Modern Alternatives to addCallback
While Twisted is powerful, its style can feel verbose. Modern Python has more ergonomic ways to handle asynchronous code.
A. async/await (The Modern Standard)
This is the preferred way to write asynchronous code in Python 3.5+. It uses coroutines and the await keyword to make asynchronous code look and behave like synchronous code, hiding the callback machinery.
# You'll need an async HTTP client like aiohttp: pip install aiohttp
import asyncio
import aiohttp
async def fetch_and_process_url():
url = 'https://www.example.com'
try:
# async with creates a context manager that handles the session
async with aiohttp.ClientSession() as session:
# await pauses this function until the HTTP request is complete
async with session.get(url) as response:
# response.text() also returns a coroutine, so we await it
content = await response.text()
print(f"--- Content received! First 100 chars: {content[:100]}...")
# We can process the data directly here
content_length = len(content)
print(f"--- Final Content Length: {content_length} characters ---")
except aiohttp.ClientError as e:
print(f"--- An error occurred: {e} ---")
# Run the async function
asyncio.run(fetch_and_process_url())
Comparison:
- Readability: The
async/awaitversion is linear and much easier to read. You don't have to mentally trace a chain of callbacks. - State Management: State is kept in the function's local variables, not passed between callback functions.
- Error Handling: Standard
try...exceptblocks work as expected.
B. concurrent.futures.Future (Standard Library)
If you're not doing network I/O but just want to run a function in a background thread or process, the concurrent.futures module is a great tool. It has a Future object, which is similar to a Deferred but for thread/process-based concurrency.

You add callbacks using add_done_callback().
import concurrent.futures
import time
def some_long_running_task(seconds):
print(f"Task started, will run for {seconds} seconds...")
time.sleep(seconds)
print("Task finished!")
return "Task completed successfully"
def future_completed_callback(future):
"""This is called when the future is done."""
try:
result = future.result() # Get the result, or raise an exception
print(f"--- Callback received result: {result} ---")
except Exception as e:
print(f"--- Callback received an error: {e} ---")
# Create an executor to manage the thread/process pool
with concurrent.futures.ThreadPoolExecutor() as executor:
# Submit the task to the executor. It returns a Future object immediately.
future = executor.submit(some_long_running_task, 3)
# Add the callback. It will be called on the main thread when the task is done.
future.add_done_callback(future_completed_callback)
print("Main program continues doing other work while the task runs in the background...")
# The program will wait here for all futures to complete before exiting.
# The callback will be executed automatically.
Summary: addCallback vs. Modern Approaches
| Feature | addCallback (Twisted) |
async/await |
concurrent.futures |
|---|---|---|---|
| Use Case | Asynchronous network I/O, event-driven systems. | Asynchronous network I/O, concurrent tasks. | CPU-bound or blocking I/O in background threads/processes. |
| Readability | Can lead to "callback hell" with complex chains. | Very high, resembles synchronous code. | High, similar to synchronous code. |
| Error Handling | Requires addErrback and Failure objects. |
Standard try...except blocks. |
Standard try...except blocks in callback. |
| Python Version | x and 3.x (Twisted is still maintained). | Recommended for new code (3.5+). | Standard library (3.2+). |
| Key Concept | Chaining functions to a Deferred promise. |
await pauses a coroutine until a result is ready. |
Submitting work to a thread/process pool. |
Conclusion:
When you see addCallback, you are almost certainly looking at code written for the Twisted framework. While it's a powerful and mature library, for new projects, especially those involving network I/O, the async/await syntax is generally the more modern, readable, and recommended approach. For general-purpose background task execution, concurrent.futures is the standard library tool of choice.
