async def fetch_pages():
for i in range(3):
await asyncio.sleep(0.1) # simulate async work
yield f"page-{i}"
Topics
20
Async Generators & Async Context Managers
Command-Line Interfaces (argparse)
Comprehensions & Generators
Concurrency (asyncio/threading/multiprocessing)
Data Types & Structures
Dataclasses & NamedTuples
Debugging & Profiling
Decorators
Descriptors & Properties
Exception Handling
File I/O & Context Managers
Functions & Scope
functools & Functional Programming Tools
Iterators & the Iterator Protocol
Logging
Magic Methods & Operator Overloading
Memory Management & Garbage Collection
Metaclasses & Class Customization
Modules & Packaging
Multiple Inheritance & MRO
Async Generators & Async Context Managers
15 questions found
An async generator is a function defined with 'async def' that uses 'yield' inside it, producing an object supporting the asynchronous iteration protocol (__anext__) instead of the regular one — it lets each item be produced after an 'await', unlike a regular generator which is fully synchronous.
Real-world example
Streaming paginated API results one page at a time, awaiting each network call before yielding the next page.
Concurrency (asyncio/threading/multiprocessing)
'async for' is the asynchronous equivalent of a regular 'for' loop — it awaits each call to the generator's __anext__() method, pausing until the next value is ready, and must itself be used inside an 'async def' function.
async def main():
async for page in fetch_pages():
print(page)
asyncio.run(main())
Real-world example
Processing streamed results from an async API client one item at a time as they arrive.
Concurrency (asyncio/threading/multiprocessing)
An async context manager implements __aenter__ and __aexit__ (both coroutines, defined with 'async def') instead of the synchronous __enter__/__exit__ — used with 'async with' to manage a resource whose setup or teardown itself requires awaiting, like an async database connection.
class AsyncConnection:
async def __aenter__(self):
await self.connect()
return self
async def __aexit__(self, exc_type, exc, tb):
await self.disconnect()
async def main():
async with AsyncConnection() as conn:
await conn.query("SELECT 1")
Real-world example
Managing an async database or HTTP client session where opening/closing the connection are themselves async operations.
Concurrency (asyncio/threading/multiprocessing)
How do you write an async context manager more concisely using @contextlib.asynccontextmanager?
Intermediateasynccontextmanager decorates a single async generator function: code before the 'yield' runs as __aenter__, the yielded value becomes the 'as' target, and code after 'yield' (typically in a try/finally) runs as __aexit__ — avoiding the need to write a full class with both dunder methods.
from contextlib import asynccontextmanager
@asynccontextmanager
async def async_connection():
conn = await connect()
try:
yield conn
finally:
await conn.close()
async def main():
async with async_connection() as conn:
await conn.query("SELECT 1")
Real-world example
Writing a lightweight, function-based async resource manager without the boilerplate of a full __aenter__/__aexit__ class.
functools & Functional Programming Tools
How does an async generator interact with try/finally for guaranteed cleanup, similar to regular generators?
IntermediateJust like a regular generator, wrapping the yield in a try/finally inside an async generator guarantees the finally block runs when the generator is exhausted, explicitly closed via aclose(), or garbage collected — ensuring resources like an open connection are properly released even if the consumer stops iterating early.
async def read_stream(conn):
try:
while True:
chunk = await conn.read_chunk()
if not chunk:
break
yield chunk
finally:
await conn.close() # runs even if consumer breaks out of 'async for' early
Real-world example
Ensuring a network connection or file handle used inside an async generator is properly closed even if the consumer stops early.
Exception Handling
How do you build an async generator pipeline, chaining one async generator's output into another, like a synchronous generator pipeline?
IntermediateDefine each stage as its own async generator that takes an async iterable as input and 'async for's over it, yielding transformed values — chaining multiple stages together creates a fully lazy, asynchronous processing pipeline, mirroring how synchronous generators compose.
async def double(source):
async for item in source:
yield item * 2
async def only_even(source):
async for item in source:
if item % 2 == 0:
yield item
async def main():
async for value in only_even(double(numbers_stream())):
print(value)
Real-world example
Building a lazy, asynchronous ETL-style pipeline that transforms and filters streamed data without buffering it all in memory.
Comprehensions & Generators
How do async comprehensions work, combining 'async for' directly inside a list/set/dict comprehension?
IntermediatePython allows 'async for' (and 'if') clauses directly inside a comprehension, as long as it's evaluated inside an async function — this asynchronously iterates the source, awaiting each step, while still producing a regular (synchronous) list/set/dict as the final result.
async def main():
results = [page async for page in fetch_pages()]
print(results) # a regular list, built by asynchronously iterating fetch_pages()
Real-world example
Collecting all results from an async generator into a plain list when you know the full result set is manageable in memory.
Comprehensions & Generators
How would you implement a custom async iterator class from scratch, without using an async generator function?
AdvancedImplement __aiter__ (returning self) and __anext__ (an async method that returns the next value or raises StopAsyncIteration when exhausted) — this is the lower-level protocol that async generator functions are automatically compiled down to by the interpreter.
class AsyncCounter:
def __init__(self, limit):
self.limit = limit
self.current = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.current >= self.limit:
raise StopAsyncIteration
await asyncio.sleep(0.01)
self.current += 1
return self.current
Real-world example
Building a custom async iterable class with more control than an async generator function provides, like exposing extra methods or state.
Iterators & the Iterator Protocol
How do you handle backpressure when an async generator produces items faster than the consumer can process them?
AdvancedSince 'async for' inherently pauses the PRODUCER (the generator's execution) at each yield until the consumer calls __anext__() again, async generators are naturally backpressure-aware — the producer literally cannot get ahead of the consumer, unlike a callback-based push model where you'd need an explicit buffer or queue.
async def slow_consumer(source):
async for item in source:
await asyncio.sleep(1) # slow processing
process(item)
# the producer naturally waits here until we call __anext__ again
Real-world example
Processing a fast-arriving async data stream (like a websocket feed) without needing to manually implement a bounded buffer or queue.
Concurrency (asyncio/threading/multiprocessing)
How would you implement timeout handling for an async generator's individual yields, so a stalled source doesn't hang the consumer forever?
AdvancedWrap each call to the generator's __anext__() in asyncio.wait_for() with a timeout, catching asyncio.TimeoutError if a specific item takes too long to arrive — since 'async for' itself doesn't provide a built-in per-item timeout mechanism.
async def with_timeout(source, timeout):
it = source.__aiter__()
while True:
try:
yield await asyncio.wait_for(it.__anext__(), timeout)
except StopAsyncIteration:
break
async def main():
async for item in with_timeout(slow_stream(), timeout=2.0):
print(item)
Real-world example
Cutting off a stalled network stream or SSE connection after a set idle period, instead of hanging indefinitely.
Exception Handling
Showing 1–10 of 15