启动异步功能而不导入asyncio包

时间:2016-02-23 19:00:55

标签: python asynchronous python-asyncio

是否可以启动这样的功能

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

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

我正在寻找一个类似于Go的goroutines的原则,其中一个人可以只用go语句启动一个协程。

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

4 个答案:

答案 0 :(得分:26)

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

在这种情况下,由于你的函数里面没有await点,所以只需要一次推动即可。您可以通过send 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

现在,所有这些.send开始变得乏味。让它们半自动生成会很好。

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只是上述的一个非常概括的变体:提示被Future替换,多个协同程序保存在队列中,因此每个协程最终都会轮到它,事件更丰富,包括网络/套接字通信,文件系统读/写,信号处理,线程/进程侧执行等。但基本的想法仍然是相同的:你抓住一些协同程序,将它们从空中转移到另一个地方,直到它们全部抬起StopIteration。当所有协同程序无关时,你会去外部世界并抓住一些额外的事件让他们咀嚼,然后继续。

我希望现在更清楚了。 : - )

答案 1 :(得分:1)

不,那是不可能的。你需要一个事件循环。看看如果你只是致电foo()

会发生什么
>>> f = foo()
>>> print(f)
<coroutine object foo at 0x7f6e13edac50>

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

答案 2 :(得分:1)

Python协同程序是生成器的一种语法糖,它们的行为有一些额外的限制(因此它们的目的明显不同而且不混合)。你做不到:

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

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

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

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

答案 3 :(得分:0)

协同程序应该能够

  1. 运行

  2. 对调用者进行控制(可选择生成一些中间体 结果)

  3. 能够从来电者处获取一些信息并恢复

  4. 所以,这是一个异步函数(也称为本地协同程序)的小型演示,它不使用asyncio或任何其他提供事件循环的模块/框架来完成它。至少需要python 3.5。请参阅代码中的注释。

    #!/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被禁止,因此需要混合使用原生和基于生成器的协程。