async exec in python
Yours problem is that you are trying to await to None
object- exec
ignores the return value from its code, and always returns None
.
If you want to execute and await to the result you should use eval
- eval
returns the value of the given expression.
Your's code should look like this:
import asyncio
async def f():
exec('x = 1')
await eval('asyncio.sleep(x)')
loop = asyncio.get_event_loop()
loop.run_until_complete(f())
loop.close()
This is based off @YouTwitFace's answer, but keeps globals unchanged, handles locals better and passes kwargs. Note multi-line strings still won't keep their formatting. Perhaps you want this?
async def aexec(code, **kwargs):
# Don't clutter locals
locs = {}
# Restore globals later
globs = globals().copy()
args = ", ".join(list(kwargs.keys()))
exec(f"async def func({args}):\n " + code.replace("\n", "\n "), {}, locs)
# Don't expect it to return from the coro.
result = await locs["func"](**kwargs)
try:
globals().clear()
# Inconsistent state
finally:
globals().update(**globs)
return result
It starts by saving the locals. It declares the function, but with a restricted local namespace so it doesn't touch the stuff declared in the aexec helper. The function is named func
and we access the locs
dict, containing the result of the exec's locals. The locs["func"]
is what we want to execute, so we call it with **kwargs
from aexec invocation, which moves these args into the local namespace. Then we await this and store it as result
. Finally, we restore locals and return the result.
Warning:
Do not use this if there is any multi-threaded code touching global variables. Go for @YouTwitFace's answer which is simpler and thread-safe, or remove the globals save/restore code
Note: F-strings are only supported in python 3.6+. For older versions, use
%s
,.format()
or the classic+
concatenation.
async def aexec(code):
# Make an async function with the code and `exec` it
exec(
f'async def __ex(): ' +
''.join(f'\n {l}' for l in code.split('\n'))
)
# Get `__ex` from local variables, call it and return the result
return await locals()['__ex']()
Known issues:
- If you use new lines in a string (triple quotes), it will mess up the formatting.