In Python 3.5+, I often end up with a situation where I have many nested coroutines just in order to call something that's deeply a coroutine, where the await merely comes in a tail call in most of the functions, like this:
import asyncio
async def deep(time):
await asyncio.sleep(time)
return time
async def c(time):
time *= 2
return await deep(time)
async def b(time):
time *= 2
return await c(time)
async def a(time):
time *= 2
return await b(time)
async def test():
print(await a(0.1))
loop = asyncio.get_event_loop()
loop.run_until_complete(test())
loop.close()
Those functions a, b, and c could be written as regular functions that return a coroutine, instead of as coroutines themselves, as follows:
import asyncio
async def deep(time):
await asyncio.sleep(time)
return time
def c(time):
time *= 2
return deep(time)
def b(time):
time *= 2
return c(time)
def a(time):
time *= 2
return b(time)
async def test():
print(await a(0.1))
loop = asyncio.get_event_loop()
loop.run_until_complete(test())
loop.close()
Which way is more Pythonic? Which way is more performant? Which way will be easier for others to maintain in the future?
Edit — Performance Measurement
As a performance test, I removed the await asyncio.sleep(time) line from deep and timed 1,000,000 iterations of await a(0.1). On my test system with CPython 3.5.2, the first version took around 2.4 second and the second version took around 1.6 seconds. So it looks like there may be a performance penalty to making everything coroutines, but it's certainly not an order of magnitude. Perhaps someone with more experience profiling Python code could create a proper benchmark and address the performance issue definitively.