From 104ce527c607d595b4cb0e50bd22f78bed37db48 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Thu, 3 Jul 2025 15:28:35 +0200 Subject: [PATCH 1/2] Sample code for the article on `asyncio` --- python-asyncio/README.md | 3 ++ python-asyncio/as_completed.py | 21 ++++++++++++++ python-asyncio/chained.py | 45 +++++++++++++++++++++++++++++ python-asyncio/countasync.py | 21 ++++++++++++++ python-asyncio/countsync.py | 20 +++++++++++++ python-asyncio/gathers.py | 21 ++++++++++++++ python-asyncio/powers.py | 23 +++++++++++++++ python-asyncio/queued.py | 52 ++++++++++++++++++++++++++++++++++ python-asyncio/rand.py | 35 +++++++++++++++++++++++ python-asyncio/tasks.py | 17 +++++++++++ python-asyncio/websites.py | 21 ++++++++++++++ 11 files changed, 279 insertions(+) create mode 100644 python-asyncio/README.md create mode 100644 python-asyncio/as_completed.py create mode 100644 python-asyncio/chained.py create mode 100644 python-asyncio/countasync.py create mode 100644 python-asyncio/countsync.py create mode 100644 python-asyncio/gathers.py create mode 100644 python-asyncio/powers.py create mode 100644 python-asyncio/queued.py create mode 100644 python-asyncio/rand.py create mode 100644 python-asyncio/tasks.py create mode 100644 python-asyncio/websites.py diff --git a/python-asyncio/README.md b/python-asyncio/README.md new file mode 100644 index 0000000000..3e3d7751c7 --- /dev/null +++ b/python-asyncio/README.md @@ -0,0 +1,3 @@ +# Async I/O in Python: A Complete Walkthrough + +This folder provides the code examples for the Real Python tutorial [Async I/O in Python: A Complete Walkthrough](https://realpython.com/async-io-python/). diff --git a/python-asyncio/as_completed.py b/python-asyncio/as_completed.py new file mode 100644 index 0000000000..b7d3373187 --- /dev/null +++ b/python-asyncio/as_completed.py @@ -0,0 +1,21 @@ +import asyncio +import time + + +async def coro(numbers): + await asyncio.sleep(min(numbers)) + return list(reversed(numbers)) + + +async def main(): + task1 = asyncio.create_task(coro([3, 2, 1])) + task2 = asyncio.create_task(coro([10, 5, 2])) + print("Start:", time.strftime("%X")) + for task in asyncio.as_completed([task1, task2]): + result = await task + print(f'result: {result} completed at {time.strftime("%X")}') + print("End:", time.strftime("%X")) + print(f"Both tasks done: {all((task1.done(), task2.done()))}") + + +asyncio.run(main()) diff --git a/python-asyncio/chained.py b/python-asyncio/chained.py new file mode 100644 index 0000000000..477b674122 --- /dev/null +++ b/python-asyncio/chained.py @@ -0,0 +1,45 @@ +import asyncio +import random +import time + + +async def fetch_user(user_id): + delay = random.uniform(0.5, 2.0) + print(f"User coro: fetching user by {user_id=}...") + await asyncio.sleep(delay) + user = {"id": user_id, "name": f"User{user_id}"} + print(f"User coro: fetched user with {user_id=} (done in {delay:.1f}s).") + return user + + +async def fetch_posts(user): + delay = random.uniform(0.5, 2.0) + print(f"Post coro: retrieving posts for {user['name']}...") + await asyncio.sleep(delay) + posts = [f"Post {i} by {user['name']}" for i in range(1, 3)] + print( + f"Post coro: got {len(posts)} posts by {user['name']}" + f" (done in {delay:.1f}s):" + ) + for post in posts: + print(f" - {post}") + + +async def get_user_with_posts(user_id): + user = await fetch_user(user_id) + await fetch_posts(user) + + +async def main(): + user_ids = [1, 2, 3] + start = time.perf_counter() + await asyncio.gather( + *(get_user_with_posts(user_id) for user_id in user_ids) + ) + end = time.perf_counter() + print(f"\n==> Total time: {end - start:.2f} seconds") + + +if __name__ == "__main__": + random.seed(444) + asyncio.run(main()) diff --git a/python-asyncio/countasync.py b/python-asyncio/countasync.py new file mode 100644 index 0000000000..a7b82de778 --- /dev/null +++ b/python-asyncio/countasync.py @@ -0,0 +1,21 @@ +import asyncio + + +async def count(): + print("One") + await asyncio.sleep(1) + print("Two") + await asyncio.sleep(1) + + +async def main(): + await asyncio.gather(count(), count(), count()) + + +if __name__ == "__main__": + import time + + start = time.perf_counter() + asyncio.run(main()) + elapsed = time.perf_counter() - start + print(f"{__file__} executed in {elapsed:0.2f} seconds.") diff --git a/python-asyncio/countsync.py b/python-asyncio/countsync.py new file mode 100644 index 0000000000..a67d6478c1 --- /dev/null +++ b/python-asyncio/countsync.py @@ -0,0 +1,20 @@ +import time + + +def count(): + print("One") + time.sleep(1) + print("Two") + time.sleep(1) + + +def main(): + for _ in range(3): + count() + + +if __name__ == "__main__": + start = time.perf_counter() + main() + elapsed = time.perf_counter() - start + print(f"{__file__} executed in {elapsed:0.2f} seconds.") diff --git a/python-asyncio/gathers.py b/python-asyncio/gathers.py new file mode 100644 index 0000000000..6600beef06 --- /dev/null +++ b/python-asyncio/gathers.py @@ -0,0 +1,21 @@ +import asyncio +import time + + +async def coro(numbers): + await asyncio.sleep(min(numbers)) + return list(reversed(numbers)) + + +async def main(): + task1 = asyncio.create_task(coro([3, 2, 1])) + task2 = asyncio.create_task(coro([10, 5, 2])) + print("Start:", time.strftime("%X")) + result = await asyncio.gather(task1, task2) + print("End:", time.strftime("%X")) + print(f"Both tasks done: {all((task1.done(), task2.done()))}") + return result + + +result = asyncio.run(main()) +print(f"result: {result}") diff --git a/python-asyncio/powers.py b/python-asyncio/powers.py new file mode 100644 index 0000000000..3959cbf571 --- /dev/null +++ b/python-asyncio/powers.py @@ -0,0 +1,23 @@ +import asyncio + + +async def powers_of_two(stop=10): + exponent = 0 + while exponent < stop: + yield 2**exponent + exponent += 1 + await asyncio.sleep(0.2) # Simulate some asynchronous work + + +async def main(): + g = [] + async for i in powers_of_two(5): + g.append(i) + print(g) + + f = [j async for j in powers_of_two(5) if not (j // 3 % 5)] + print(f) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python-asyncio/queued.py b/python-asyncio/queued.py new file mode 100644 index 0000000000..881980995b --- /dev/null +++ b/python-asyncio/queued.py @@ -0,0 +1,52 @@ +import asyncio +import random +import time + + +async def producer(queue, user_ids): + async def fetch_user(user_id): + delay = random.uniform(0.5, 2.0) + print(f"Producer: fetching user by {user_id=}...") + await asyncio.sleep(delay) + user = {"id": user_id, "name": f"User{user_id}"} + print(f"Producer: fetched user with {user_id=} (done in {delay:.1f}s)") + await queue.put(user) + + await asyncio.gather(*(fetch_user(uid) for uid in user_ids)) + for _ in range(len(user_ids)): + await queue.put(None) # Sentinels for consumers to terminate + + +async def consumer(queue): + while True: + user = await queue.get() + if user is None: + break + delay = random.uniform(0.5, 2.0) + print(f"Consumer: retrieving posts for {user['name']}...") + await asyncio.sleep(delay) + posts = [f"Post {i} by {user['name']}" for i in range(1, 3)] + print( + f"Consumer: got {len(posts)} posts by {user['name']}" + f" (done in {delay:.1f}s):" + ) + for post in posts: + print(f" - {post}") + + +async def main(): + queue = asyncio.Queue() + user_ids = [1, 2, 3] + + start = time.perf_counter() + await asyncio.gather( + producer(queue, user_ids), + *(consumer(queue) for _ in user_ids), + ) + end = time.perf_counter() + print(f"\n==> Total time: {end - start:.2f} seconds") + + +if __name__ == "__main__": + random.seed(444) + asyncio.run(main()) diff --git a/python-asyncio/rand.py b/python-asyncio/rand.py new file mode 100644 index 0000000000..4bf031112d --- /dev/null +++ b/python-asyncio/rand.py @@ -0,0 +1,35 @@ +import asyncio +import random + +COLORS = ( + "\033[0m", # End of color + "\033[36m", # Cyan + "\033[91m", # Red + "\033[35m", # Magenta +) + + +async def makerandom(delay, threshold=6): + color = COLORS[delay] + print(f"{color}Initiated makerandom({delay}).") + while (number := random.randint(0, 10)) <= threshold: + print(f"{color}makerandom({delay}) == {number} too low; retrying.") + await asyncio.sleep(delay) + print(f"{color}---> Finished: makerandom({delay}) == {number}" + COLORS[0]) + return number + + +async def main(): + result = await asyncio.gather( + makerandom(1, 9), + makerandom(2, 8), + makerandom(3, 8), + ) + return result + + +if __name__ == "__main__": + random.seed(444) + r1, r2, r3 = asyncio.run(main()) + print() + print(f"r1: {r1}, r2: {r2}, r3: {r3}") diff --git a/python-asyncio/tasks.py b/python-asyncio/tasks.py new file mode 100644 index 0000000000..3e9b594ef3 --- /dev/null +++ b/python-asyncio/tasks.py @@ -0,0 +1,17 @@ +import asyncio + + +async def coro(numbers): + await asyncio.sleep(min(numbers)) + return list(reversed(numbers)) + + +async def main(): + task = asyncio.create_task(coro([3, 2, 1])) + print(f"task: type -> {type(task)}") + print(f"task: done -> {task.done()}") + return await task + + +result = asyncio.run(main()) +print(f"result: {result}") diff --git a/python-asyncio/websites.py b/python-asyncio/websites.py new file mode 100644 index 0000000000..d46ab2a10b --- /dev/null +++ b/python-asyncio/websites.py @@ -0,0 +1,21 @@ +import asyncio + +import aiohttp + + +async def check(url): + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + print(f"{url}: status -> {response.status}") + + +async def main(): + websites = [ + "https://realpython.com", + "https://pycoders.com", + "https://www.python.org", + ] + await asyncio.gather(*(check(url) for url in websites)) + + +asyncio.run(main()) From c7fdb6a59c5cf2c75e87d4764abcc54d30fccec4 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Fri, 11 Jul 2025 16:51:24 +0200 Subject: [PATCH 2/2] TR updates, first round --- python-asyncio/as_completed.py | 4 ++-- python-asyncio/chained.py | 30 ++++++++++++++--------------- python-asyncio/except_group.py | 35 ++++++++++++++++++++++++++++++++++ python-asyncio/gathers.py | 4 ++-- python-asyncio/queued.py | 26 ++++++++++++------------- python-asyncio/rand.py | 17 ++++++++--------- python-asyncio/tasks.py | 4 ++-- 7 files changed, 77 insertions(+), 43 deletions(-) create mode 100644 python-asyncio/except_group.py diff --git a/python-asyncio/as_completed.py b/python-asyncio/as_completed.py index b7d3373187..3cba9f3c93 100644 --- a/python-asyncio/as_completed.py +++ b/python-asyncio/as_completed.py @@ -8,8 +8,8 @@ async def coro(numbers): async def main(): - task1 = asyncio.create_task(coro([3, 2, 1])) - task2 = asyncio.create_task(coro([10, 5, 2])) + task1 = asyncio.create_task(coro([10, 5, 2])) + task2 = asyncio.create_task(coro([3, 2, 1])) print("Start:", time.strftime("%X")) for task in asyncio.as_completed([task1, task2]): result = await task diff --git a/python-asyncio/chained.py b/python-asyncio/chained.py index 477b674122..c9df237e98 100644 --- a/python-asyncio/chained.py +++ b/python-asyncio/chained.py @@ -3,6 +3,21 @@ import time +async def main(): + user_ids = [1, 2, 3] + start = time.perf_counter() + await asyncio.gather( + *(get_user_with_posts(user_id) for user_id in user_ids) + ) + end = time.perf_counter() + print(f"\n==> Total time: {end - start:.2f} seconds") + + +async def get_user_with_posts(user_id): + user = await fetch_user(user_id) + await fetch_posts(user) + + async def fetch_user(user_id): delay = random.uniform(0.5, 2.0) print(f"User coro: fetching user by {user_id=}...") @@ -25,21 +40,6 @@ async def fetch_posts(user): print(f" - {post}") -async def get_user_with_posts(user_id): - user = await fetch_user(user_id) - await fetch_posts(user) - - -async def main(): - user_ids = [1, 2, 3] - start = time.perf_counter() - await asyncio.gather( - *(get_user_with_posts(user_id) for user_id in user_ids) - ) - end = time.perf_counter() - print(f"\n==> Total time: {end - start:.2f} seconds") - - if __name__ == "__main__": random.seed(444) asyncio.run(main()) diff --git a/python-asyncio/except_group.py b/python-asyncio/except_group.py new file mode 100644 index 0000000000..015daa3e2f --- /dev/null +++ b/python-asyncio/except_group.py @@ -0,0 +1,35 @@ +import asyncio + + +async def coro_a(): + await asyncio.sleep(1) + raise ValueError("Error in coro A") + + +async def coro_b(): + await asyncio.sleep(2) + raise TypeError("Error in coro B") + + +async def coro_c(): + await asyncio.sleep(0.5) + raise IndexError("Error in coro C") + + +async def main(): + results = await asyncio.gather( + coro_a(), coro_b(), coro_c(), return_exceptions=True + ) + exceptions = [e for e in results if isinstance(e, Exception)] + if exceptions: + raise ExceptionGroup("Errors", exceptions) + + +try: + asyncio.run(main()) +except* ValueError as ve_group: + print(f"[ValueError handled] {ve_group.exceptions}") +except* TypeError as te_group: + print(f"[TypeError handled] {te_group.exceptions}") +except* IndexError as ie_group: + print(f"[IndexError handled] {ie_group.exceptions}") diff --git a/python-asyncio/gathers.py b/python-asyncio/gathers.py index 6600beef06..2feb06629d 100644 --- a/python-asyncio/gathers.py +++ b/python-asyncio/gathers.py @@ -8,8 +8,8 @@ async def coro(numbers): async def main(): - task1 = asyncio.create_task(coro([3, 2, 1])) - task2 = asyncio.create_task(coro([10, 5, 2])) + task1 = asyncio.create_task(coro([10, 5, 2])) + task2 = asyncio.create_task(coro([3, 2, 1])) print("Start:", time.strftime("%X")) result = await asyncio.gather(task1, task2) print("End:", time.strftime("%X")) diff --git a/python-asyncio/queued.py b/python-asyncio/queued.py index 881980995b..701d7406fb 100644 --- a/python-asyncio/queued.py +++ b/python-asyncio/queued.py @@ -3,6 +3,19 @@ import time +async def main(): + queue = asyncio.Queue() + user_ids = [1, 2, 3] + + start = time.perf_counter() + await asyncio.gather( + producer(queue, user_ids), + *(consumer(queue) for _ in user_ids), + ) + end = time.perf_counter() + print(f"\n==> Total time: {end - start:.2f} seconds") + + async def producer(queue, user_ids): async def fetch_user(user_id): delay = random.uniform(0.5, 2.0) @@ -34,19 +47,6 @@ async def consumer(queue): print(f" - {post}") -async def main(): - queue = asyncio.Queue() - user_ids = [1, 2, 3] - - start = time.perf_counter() - await asyncio.gather( - producer(queue, user_ids), - *(consumer(queue) for _ in user_ids), - ) - end = time.perf_counter() - print(f"\n==> Total time: {end - start:.2f} seconds") - - if __name__ == "__main__": random.seed(444) asyncio.run(main()) diff --git a/python-asyncio/rand.py b/python-asyncio/rand.py index 4bf031112d..db4bbe9335 100644 --- a/python-asyncio/rand.py +++ b/python-asyncio/rand.py @@ -9,6 +9,14 @@ ) +async def main(): + return await asyncio.gather( + makerandom(1, 9), + makerandom(2, 8), + makerandom(3, 8), + ) + + async def makerandom(delay, threshold=6): color = COLORS[delay] print(f"{color}Initiated makerandom({delay}).") @@ -19,15 +27,6 @@ async def makerandom(delay, threshold=6): return number -async def main(): - result = await asyncio.gather( - makerandom(1, 9), - makerandom(2, 8), - makerandom(3, 8), - ) - return result - - if __name__ == "__main__": random.seed(444) r1, r2, r3 = asyncio.run(main()) diff --git a/python-asyncio/tasks.py b/python-asyncio/tasks.py index 3e9b594ef3..290a890c2f 100644 --- a/python-asyncio/tasks.py +++ b/python-asyncio/tasks.py @@ -8,8 +8,8 @@ async def coro(numbers): async def main(): task = asyncio.create_task(coro([3, 2, 1])) - print(f"task: type -> {type(task)}") - print(f"task: done -> {task.done()}") + print(f"{type(task) = }") + print(f"{task.done() = }") return await task