我想在异步函数中调用exec并执行类似下面的代码(无效):
import asyncio
async def f():
await exec('x = 1\n' 'await asyncio.sleep(x)')
更准确地说,我希望能够在exec中运行的代码中等待未来。
如何实现这一目标?
答案 0 :(得分:6)
您的问题是您正试图等待None
对象 - exec
忽略其代码中的返回值,并始终返回None
。
如果要执行并等待结果,则应使用eval
- eval
返回给定表达式的值。
您的代码应如下所示:
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()
答案 1 :(得分:2)
感谢所有建议。我发现这可以通过同步的greenlet完成,因为greenlets允许执行"顶级等待":
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()
答案 2 :(得分:2)
注意:仅在python 3.6及更高版本中支持F字符串。对于较旧的版本,请使用
%s
,.format()
或经典的+
串联。
async def execute(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']()
答案 3 :(得分:1)
这是基于@YouTwitFace的回答,但不会使全局变量保持不变,可以更好地处理本地变量,并且可以传递kwargs。请注意,多行字符串仍然不会保留其格式。也许您想要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
字典,其中包含执行程序本地变量的结果。 locs["func"]
是我们要执行的,因此我们从aexec调用中使用**kwargs
对其进行调用,该调用将这些args移入本地名称空间。然后,我们等待它并将其存储为result
。最后,我们还原本地人并返回结果。
警告:
如果有任何涉及全局的多线程代码,请不要使用它 变量。 寻找更简单且线程安全的@YouTwitFace答案,或删除全局变量保存/恢复代码
答案 4 :(得分:0)
这里是module,使用 AST 进行操作。这意味着多行字符串将完美工作,并且行号将与原始语句匹配。另外,如果任何东西都是表达式,则将其返回(如果有多个,则作为列表,否则作为一个元素)
我制作了这个模块(有关内部工作的更多详细信息,请查看此答案的修订历史)。我用它here
答案 5 :(得分:0)
以下是使用内置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)
答案 6 :(得分:0)
只需使用此功能:
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没有返回值,使用t[0]存储一个asyncio的future,在exec外等待future获取返回值。