【问题标题】:Start async function without importing the asyncio package在不导入 asyncio 包的情况下启动 async 功能
【发布时间】:2016-02-23 19:00:55
【问题描述】:

是否可以启动这样的功能

async def foo():
    while True:
        print("Hello!")

不导入asyncio 包(并获取事件循环)?

我正在寻找一种类似于 Go 的 goroutines 的原理,其中只需 go 语句即可启动协程。

编辑:我不导入 asyncio 包的原因仅仅是因为我认为应该可以在没有事件循环的情况下启动协程(显式)。我不明白为什么 async def 和类似的语句是核心语言的一部分(甚至是语法的一部分),并且启动创建的协程的方式只能通过包获得。

【问题讨论】:

  • 关于 Python 3.5 中真正的协程,我对此表示怀疑,因为可以这么说,有些东西必须转动轮子。虽然你可以尝试通过创建一个 python 生成器并用send() 语句提供它来以“老式”的方式实现一个协程
  • 为什么不想导入asyncio

标签: python asynchronous python-asyncio


【解决方案1】:

当然可以在不显式使用asyncio 的情况下启动async 函数。毕竟,asyncio 是用 Python 编写的,所以它所做的一切,你也可以做(尽管有时你可能需要其他模块,如 selectorsthreading,如果你打算同时等待外部事件,或者并行执行一些其他代码)。

在这种情况下,由于您的函数内部没有 await 点,因此只需按一下即可。您通过 sending None 将协程推入其中。

>>> foo().send(None)
Hello!
Hello!
...

当然,如果你的函数(协程)内部有 yield 表达式,它会在每个 yield 点暂停执行,并且你需要将其他值推入其中(通过 coro.send(value)next(gen)) - 但如果你知道生成器是如何工作的,你就已经知道了。

import types

@types.coroutine
def bar():
    to_print = yield 'What should I print?'
    print('Result is', to_print)
    to_return = yield 'And what should I return?'
    return to_return

>>> b = bar()
>>> next(b)
'What should I print?'
>>> b.send('Whatever you want')
Result is Whatever you want
'And what should I return?'
>>> b.send(85)
Traceback...
StopIteration: 85

现在,如果您的函数内部有 await 表达式,它会在评估每个表达式时暂停。

async def baz():
    first_bar, second_bar = bar(), bar()
    print('Sum of two bars is', await first_bar + await second_bar)
    return 'nothing important'

>>> t = baz()
>>> t.send(None)
'What should I print?'
>>> t.send('something')
Result is something
'And what should I return?'
>>> t.send(35)
'What should I print?'
>>> t.send('something else')
Result is something else
'And what should I return?'
>>> t.send(21)
Sum of two bars is 56
Traceback...
StopIteration: nothing important

现在,所有这些.sends 都开始变得乏味了。最好能半自动生成它们。

import random, string

def run_until_complete(t):
    prompt = t.send(None)
    try:
        while True:
            if prompt == 'What should I print?':
                prompt = t.send(random.choice(string.ascii_uppercase))
            elif prompt == 'And what should I return?':
                prompt = t.send(random.randint(10, 50))
            else:
                raise ValueError(prompt)
    except StopIteration as exc:
        print(t.__name__, 'returned', exc.value)
        t.close()

>>> run_until_complete(baz())
Result is B
Result is M
Sum of two bars is 56
baz returned nothing important

恭喜,您刚刚编写了您的第一个事件循环! (没想到它会发生,是吗?;)当然,它非常原始:它只知道如何处理两种类型的提示,它不能让t 生成与它同时运行的额外协程,它通过random 生成器伪造事件。

(事实上,如果你想深入浅出:我们在上面手动执行的操作,也可以称为事件循环:Python REPL 将提示打印到控制台窗口,它依赖于您可以通过在其中输入t.send(whatever) 来提供事件。:)

asyncio 只是上面的一个非常通用的变体:提示被Futures 取代,多个协程保持在队列中,因此最终轮到它们每个,事件更丰富,包括网络/套接字通信、文件系统读/写、信号处理、线程/进程侧执行等。但是基本的想法还是一样的:你抓住一些协程,在空中将它们从一个路由到另一个,直到它们都加注StopIteration。当所有协程都无事可做时,你去外部世界抓取一些额外的事件让它们咀嚼,然后继续。

我希望现在一切都清楚多了。 :-)

【讨论】:

  • 太棒了,我迫不及待地想测试一下!
  • 如果您还有其他问题,请尽管提问。我很乐意回答。
【解决方案2】:

Python 协程是生成器的语法糖,它们的行为有一些额外的限制(因此它们的目的明显不同并且不会混用)。你不能这样做:

next(foo())
TypeError: 'coroutine' object is not an iterator

因为它已被明确禁用。但是你可以这样做:

foo().send(None)
Hello
Hello
Hello
...

对于生成器,这相当于next()

【讨论】:

    【解决方案3】:

    协程应该能够

    1. 运行

    2. 让给调用者控制(可选地产生一些中间 结果)

    3. 能够从调用者那里获取一些信息并恢复

    所以,这里有一个异步函数(又名本机协程)的小演示,它在不使用 asyncio 或任何其他提供事件循环的模块/框架的情况下执行此操作。至少需要 python 3.5。见代码里面的 cmets。

    #!/usr/bin/env python
    
    import types
    
    # two simple async functions
    async def outer_af(x):
        print("- start outer_af({})".format(x))
        val = await inner_af(x)  # Normal way to call native coroutine.
                                 # Without `await` keyword it wouldn't
                                 # actually start
        print("- inner_af result: {}".format(val))
        return "outer_af_result"
    
    
    async def inner_af(x):
        print("-- start inner_af({})".format(x))
        val = await receiver()  # 'await' can be used not only with native
                                # coroutines, but also with `generator-based`
                                # coroutines!
        print("-- received val {}".format(val))
        return "inner_af_result"
    
    
    # To yiled execution control to caller it's necessary to use
    # 'generator-based' coroutine: the one created with types.coroutine
    # decorator
    @types.coroutine
    def receiver():
        print("--- start receiver")
        # suspend execution / yield control / communicate with caller
        r = yield "value request"
        print("--- receiver received {}".format(r))
        return r
    
    def main():
        # We want to call 'outer_af' async function (aka native coroutine)
        # 'await' keyword can't be used here!
        # It can only be used inside another async function.
        print("*** test started")
        c = outer_af(42)  # just prepare coroutine object. It's not running yet.
        print("*** c is {}".format(c))
    
        # To start coroutine execution call 'send' method.
        w = c.send(None)  # The first call must have argument None
    
        # Execution of coroutine is now suspended. Execution point is on
        # the 'yield' statement inside the 'receiver' coroutine.
        # It is waiting for another 'send' method to continue.
        # The yielded value can give us a hint about what exectly coroutine
        # expects to receive from us.
        print("*** w = {}".format(w))
    
        # After next 'send' the coroutines execution would finish.
        # Even though the native coroutine object is not iterable it will
        # throw StopIteration exception on exit!
        try:
            w = c.send(25)
            # w here would not get any value. This is unreachable.
        except StopIteration as e:
            print("*** outer_af finished. It returned: {}".format(e.value))
    
    
    if __name__ == '__main__':
        main()
    

    输出如下:

    *** test started
    *** c is <coroutine object outer_af at 0x7f4879188620>
    - start outer_af(42)
    -- start inner_af(42)
    --- start receiver
    *** w = value request
    --- receiver received 25
    -- received val 25
    - inner_af result: inner_af_result
    *** outer_af finished. It returned: outer_af_result
    

    附加评论。 看起来不可能从本机协程内部产生控制。 yield 不允许在 async 函数中使用!所以有必要import types和使用coroutine装饰器。它做了一些黑魔法!坦率地说,我不明白为什么 yield 被禁止,因此需要混合使用本地和基于生成器的协程。

    【讨论】:

      【解决方案4】:

      不,这是不可能的。你需要一个事件循环。看看如果你只是打电话给foo()会发生什么:

      >>> f = foo()
      >>> print(f)
      <coroutine object foo at 0x7f6e13edac50>
      

      所以你得到了一个协程对象,现在什么都没有执行!只有将它传递给事件循环,它才会被执行。您可以使用asyncio 或其他事件循环,例如Curio

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-01-02
        • 2014-12-15
        • 1970-01-01
        • 1970-01-01
        • 2021-10-11
        • 1970-01-01
        • 2012-07-25
        • 2018-09-25
        相关资源
        最近更新 更多