python

时间:2017-07-01 09:01:34

标签: python python-3.x exec python-asyncio

我想在异步函数中调用exec并执行类似下面的代码(无效):

import asyncio

async def f():
    await exec('x = 1\n' 'await asyncio.sleep(x)')

更准确地说,我希望能够在exec中运行的代码中等待未来。

如何实现这一目标?

7 个答案:

答案 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获取返回值。