python 中的异步执行
async exec in python
我想在异步函数中调用 exec 并执行类似于以下代码(无效)的操作:
import asyncio
async def f():
await exec('x = 1\n' 'await asyncio.sleep(x)')
更准确地说,我希望能够在 exec 中运行的代码中等待未来。
如何实现?
你的问题是你正试图等待 None
对象 - exec
忽略其代码中的 return 值,并且总是 returns None
.
如果你想执行并等待结果,你应该使用 eval
- eval
returns 给定表达式的值。
您的代码应如下所示:
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()
感谢所有建议。我发现这可以通过异步的 greenlets 来完成,因为 greenlets 允许执行 "top level await":
import greenlet
import asyncio
class GreenAwait:
def __init__(self, child):
self.current = greenlet.getcurrent()
self.value = None
self.child = child
def __call__(self, future):
self.value = future
self.current.switch()
def __iter__(self):
while self.value is not None:
yield self.value
self.value = None
self.child.switch()
def gexec(code):
child = greenlet.greenlet(exec)
gawait = GreenAwait(child)
child.switch(code, {'gawait': gawait})
yield from gawait
async def aexec(code):
green = greenlet.greenlet(gexec)
gen = green.switch(code)
for future in gen:
await future
# modified asyncio example from Python docs
CODE = ('import asyncio\n'
'import datetime\n'
'async def display_date():\n'
' for i in range(5):\n'
' print(datetime.datetime.now())\n'
' await asyncio.sleep(1)\n')
def loop():
loop = asyncio.get_event_loop()
loop.run_until_complete(aexec(CODE + 'gawait(display_date())'))
loop.close()
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']()
已知问题:
- 如果您在字符串中使用换行符(三引号),会弄乱格式。
这是基于@YouTwitFace 的回答,但保持全局变量不变,更好地处理局部变量并传递 kwargs。注意 multi-line 字符串仍然不会保留其格式。也许你想要 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
首先要拯救当地人。它声明函数,但具有受限的本地名称空间,因此它不会触及在 aexec 帮助程序中声明的内容。该函数名为 func
并且我们访问 locs
dict,其中包含 exec 的本地结果。 locs["func"]
是我们想要执行的,因此我们在 aexec 调用中用 **kwargs
调用它,这会将这些 args 移动到本地名称空间中。然后我们等待这个并将其存储为 result
。最后,我们恢复 locals 和 return 结果。
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
这是一个 module 使用 AST 来做事。这意味着多行字符串将完美地工作并且行号将与原始语句匹配。另外,如果任何东西是一个表达式,它被返回(如果有多个,则作为一个列表,否则只是一个元素)
我制作了这个模块(查看此答案的修订历史以了解有关内部工作的更多详细信息)。我用它here
这是使用内置 ast
模块的更可靠的方法:
import ast
async def async_exec(stmts, env=None):
parsed_stmts = ast.parse(stmts)
fn_name = "_async_exec_f"
fn = f"async def {fn_name}(): pass"
parsed_fn = ast.parse(fn)
for node in parsed_stmts.body:
ast.increment_lineno(node)
parsed_fn.body[0].body = parsed_stmts.body
exec(compile(parsed_fn, filename="<ast>", mode="exec"), env)
return await eval(f"{fn_name}()", env)
只需使用这个功能:
import asyncio
async def async_exec(code):
t = [None]
exec('async def _async_exec():\n return {}\nt[0] = asyncio.ensure_future(_async_exec())'.format(code))
return await t[0]
这里有一个代码示例,可以直接运行。 (适用于 Python 3.6.8)
import asyncio
async def async_exec(code):
t = [None]
exec('async def _async_exec():\n return {}\nt[0] = asyncio.ensure_future(_async_exec())'.format(code))
return await t[0]
async def p(s):
await asyncio.sleep(s)
return s
async def main():
print(await async_exec('await p(0.1) / await p(0.2)'))
asyncio.get_event_loop().run_until_complete(main())
我试着解释一下,在exec中定义一个async函数。在异步函数中,运行 你想要的代码。但是 exec 没有 return 值,使用 t[0] 存储一个 asyncio 未来,在 exec 之外等待未来以获得 return 值。
我想在异步函数中调用 exec 并执行类似于以下代码(无效)的操作:
import asyncio
async def f():
await exec('x = 1\n' 'await asyncio.sleep(x)')
更准确地说,我希望能够在 exec 中运行的代码中等待未来。
如何实现?
你的问题是你正试图等待 None
对象 - exec
忽略其代码中的 return 值,并且总是 returns None
.
如果你想执行并等待结果,你应该使用 eval
- eval
returns 给定表达式的值。
您的代码应如下所示:
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()
感谢所有建议。我发现这可以通过异步的 greenlets 来完成,因为 greenlets 允许执行 "top level await":
import greenlet
import asyncio
class GreenAwait:
def __init__(self, child):
self.current = greenlet.getcurrent()
self.value = None
self.child = child
def __call__(self, future):
self.value = future
self.current.switch()
def __iter__(self):
while self.value is not None:
yield self.value
self.value = None
self.child.switch()
def gexec(code):
child = greenlet.greenlet(exec)
gawait = GreenAwait(child)
child.switch(code, {'gawait': gawait})
yield from gawait
async def aexec(code):
green = greenlet.greenlet(gexec)
gen = green.switch(code)
for future in gen:
await future
# modified asyncio example from Python docs
CODE = ('import asyncio\n'
'import datetime\n'
'async def display_date():\n'
' for i in range(5):\n'
' print(datetime.datetime.now())\n'
' await asyncio.sleep(1)\n')
def loop():
loop = asyncio.get_event_loop()
loop.run_until_complete(aexec(CODE + 'gawait(display_date())'))
loop.close()
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']()
已知问题:
- 如果您在字符串中使用换行符(三引号),会弄乱格式。
这是基于@YouTwitFace 的回答,但保持全局变量不变,更好地处理局部变量并传递 kwargs。注意 multi-line 字符串仍然不会保留其格式。也许你想要 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
首先要拯救当地人。它声明函数,但具有受限的本地名称空间,因此它不会触及在 aexec 帮助程序中声明的内容。该函数名为 func
并且我们访问 locs
dict,其中包含 exec 的本地结果。 locs["func"]
是我们想要执行的,因此我们在 aexec 调用中用 **kwargs
调用它,这会将这些 args 移动到本地名称空间中。然后我们等待这个并将其存储为 result
。最后,我们恢复 locals 和 return 结果。
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
这是一个 module 使用 AST 来做事。这意味着多行字符串将完美地工作并且行号将与原始语句匹配。另外,如果任何东西是一个表达式,它被返回(如果有多个,则作为一个列表,否则只是一个元素)
我制作了这个模块(查看此答案的修订历史以了解有关内部工作的更多详细信息)。我用它here
这是使用内置 ast
模块的更可靠的方法:
import ast
async def async_exec(stmts, env=None):
parsed_stmts = ast.parse(stmts)
fn_name = "_async_exec_f"
fn = f"async def {fn_name}(): pass"
parsed_fn = ast.parse(fn)
for node in parsed_stmts.body:
ast.increment_lineno(node)
parsed_fn.body[0].body = parsed_stmts.body
exec(compile(parsed_fn, filename="<ast>", mode="exec"), env)
return await eval(f"{fn_name}()", env)
只需使用这个功能:
import asyncio
async def async_exec(code):
t = [None]
exec('async def _async_exec():\n return {}\nt[0] = asyncio.ensure_future(_async_exec())'.format(code))
return await t[0]
这里有一个代码示例,可以直接运行。 (适用于 Python 3.6.8)
import asyncio
async def async_exec(code):
t = [None]
exec('async def _async_exec():\n return {}\nt[0] = asyncio.ensure_future(_async_exec())'.format(code))
return await t[0]
async def p(s):
await asyncio.sleep(s)
return s
async def main():
print(await async_exec('await p(0.1) / await p(0.2)'))
asyncio.get_event_loop().run_until_complete(main())
我试着解释一下,在exec中定义一个async函数。在异步函数中,运行 你想要的代码。但是 exec 没有 return 值,使用 t[0] 存储一个 asyncio 未来,在 exec 之外等待未来以获得 return 值。