Python  

Python async function returning coroutine object

Introduction

In Python, when you define a function with async def, it does not run like a normal function. Instead, calling it creates something called a coroutine object.

A coroutine object is like a "promise" of future work. It represents the task but does not do anything until you explicitly tell Python to run it. If you print it directly, you will see something like:

async def greet():
    return "Hello from async!"

print(greet())  
# Output: <coroutine object greet at 0x...>

This happens because Python is waiting for you to await the coroutine before executing it.

How to Properly Run an Async Function in Python

To get the actual result from a coroutine, you must either:

  • Use the await keyword inside another async function.

  • Or use asyncio.run() at the top level of your program.

Example with await:

import asyncio

async def greet():
    return "Hello from async!"

async def main():
    message = await greet()
    print(message)

asyncio.run(main())

This will finally print:

Hello from async!

Why Does Python Do This?

Python separates defining work from running work when using async.

  • A normal function runs immediately when called.

  • An async function only prepares a coroutine object, and you must explicitly run it.

This design helps Python manage multiple tasks at once, such as web requests, database queries, or file operations, without blocking the program.

Common Mistake: Calling Async Function Without Await

A very common error beginners face is forgetting to use await.

async def get_number():
    return 42

result = get_number()
print(result)  
# Prints: <coroutine object get_number at 0x...>

Fix:

import asyncio

async def get_number():
    return 42

async def main():
    result = await get_number()
    print(result)

asyncio.run(main())

Now it prints the actual number:

42

Using Async with External Libraries

Many modern Python libraries such as aiohttp, httpx, and frameworks like FastAPI use async for better performance.

Example: fetching data from an API with aiohttp:

import aiohttp
import asyncio

async def fetch_data():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://httpbin.org/get") as response:
            return await response.json()

async def main():
    data = await fetch_data()
    print(data)

asyncio.run(main())

Here, without await, you would only get a coroutine object instead of real data.

Running Multiple Async Tasks Together

Python async allows you to run many tasks at the same time using asyncio.gather(). This avoids waiting for one task to finish before starting another.

import asyncio

async def task(name, delay):
    await asyncio.sleep(delay)
    return f"Task {name} finished in {delay} seconds"

async def main():
    results = await asyncio.gather(
        task("A", 2),
        task("B", 1)
    )
    print(results)

asyncio.run(main())

Output:

['Task A finished in 2 seconds', 'Task B finished in 1 seconds']

This is useful for APIs, database queries, or file operations where tasks can run side by side.

Visual Flow of Async Execution

flowchart TD
    A[Call async function] --> B[Python returns coroutine object]
    B --> C[Use await inside another async function]
    C --> D[Coroutine executes and returns real value]

Fixes for Common Coroutine Issues

  • Problem: Async function returns <coroutine object> instead of result.
    Fix: Add await before the function call.

  • Problem: await used in a normal function.
    Fix: Wrap the code in an async function and call it with asyncio.run().

  • Problem: Blocking code inside async.
    Fix: Replace blocking code with async alternatives or run blocking tasks in a separate thread with asyncio.to_thread().

FAQs

Q: Why do I see <coroutine object> instead of the result?
A: Because async functions don’t run automatically. You must use await or asyncio.run().

Q: Can I mix normal functions with async functions?
A: Yes, but when you need results from an async function, you must call it inside another async function with await.

Q: What is the difference between await and asyncio.run()?
A: Use await when you’re already inside an async function. Use asyncio.run() when starting your program.

Q: Why should I use async in Python?
A: Async is useful when you need to handle many tasks at once, like API calls, database operations, or network communication, without blocking the program.

Summary

When a Python async function returns a coroutine object, it’s completely normal. It means the function is waiting to be run. To get the real result, you must use await inside another async function or run it with asyncio.run(). Using async properly allows you to handle multiple tasks at the same time, making your Python programs faster and more efficient for web requests, databases, and APIs.