understanding partial() with async/futures
the basic idea
partial "freezes" args in a fn so you don't have to pass them every time
from functools import partial
def add(a, b, c):
return a + b + c
add_5_and_10 = partial(add, 5, 10)
add_5_and_10(3) # returns 18 (same as add(5, 10, 3))the problem: fetching from multiple APIs
imagine you need to fetch user data from 3 different API endpoints at the same time
here's the messy way:
import asyncio
from functools import partial
from concurrent.futures import ThreadPoolExecutor
def fetch_data(user_id, api_endpoint, timeout=30, retry=3, api_key="secret"):
return f"Data from {api_endpoint} for user {user_id}"
async def get_user_data_messy(user_id):
executor = ThreadPoolExecutor()
loop = asyncio.get_event_loop()
# repetition
future1 = loop.run_in_executor(
executor,
lambda: fetch_data(user_id, "profile", 30, 3, "secret")
)
future2 = loop.run_in_executor(
executor,
lambda: fetch_data(user_id, "orders", 30, 3, "secret")
)
future3 = loop.run_in_executor(
executor,
lambda: fetch_data(user_id, "reviews", 30, 3, "secret")
)
results = await asyncio.gather(future1, future2, future3)
return resultsthe clean way with partial:
async def get_user_data_clean(user_id):
executor = ThreadPoolExecutor()
loop = asyncio.get_event_loop()
# common way
fetcher = partial(
fetch_data,
user_id=user_id,
timeout=30,
retry=3,
api_key="secret"
)
endpoints = ["profile", "orders", "reviews"]
futures = [
loop.run_in_executor(executor, partial(fetcher, api_endpoint=ep))
for ep in endpoints
]
results = await asyncio.gather(*futures)
return resultswhy the double partial
loop.run_in_executor(executor, partial(fetcher, api_endpoint=ep))here's what's actually happening:
# first partial: lock in the common stuff
fetcher = partial(fetch_data, user_id=user_id, timeout=30, retry=3, api_key="secret")
# second partial: add the specific endpoint
profile_fetcher = partial(fetcher, api_endpoint="profile")
# now profile_fetcher() is a zero-argument callable
# calling it is the same as: fetch_data(user_id, "profile", 30, 3, "secret")seeing it run
import time
def fetch_data(user_id, api_endpoint, timeout=30, retry=3, api_key="secret"):
time.sleep(1) # pretend this is an API call
return f"Data from {api_endpoint} for user {user_id}"
async def main():
start = time.time()
results = await get_user_data_clean(12345)
print(f"completed in {time.time() - start:.2f}s")
print(results)
# completed in 1.01s (all 3 APIs ran at the same time)
# ['Data from profile for user 12345',
# 'Data from orders for user 12345',
# 'Data from reviews for user 12345']
asyncio.run(main())