从流中产生的正确方法是什么?

时间:2017-08-26 21:52:33

标签: python python-3.x generator python-asyncio yield-from

我有一个Connection对象,用于包含asyncio个连接的读写流:

class Connection(object):

    def __init__(self, stream_in, stream_out):
        object.__init__(self)

        self.__in = stream_in
        self.__out = stream_out

    def read(self, n_bytes : int = -1):
        return self.__in.read(n_bytes)

    def write(self, bytes_ : bytes):
        self.__out.write(bytes_)
        yield from self.__out.drain()

在服务器端,connected每次客户端连接时都会创建一个Connection对象,然后读取4个字节。

@asyncio.coroutine
def new_conection(stream_in, stream_out):
    conn = Connection(stream_in, stream_out)
    data = yield from conn.read(4)
    print(data)

在客户端,写出4个字节。

@asyncio.coroutine
def client(loop):
    ...
    conn = Connection(stream_in, stream_out)
    yield from conn.write(b'test')

此功能几乎与预期一致,但每次yield fromread呼叫都必须write。我在yield from内尝试了Connection

def read(self, n_bytes : int = -1):
    data = yield from self.__in.read(n_bytes)
    return data

但是我没有获取数据,而是得到了像

这样的输出
<generator object StreamReader.read at 0x1109983b8>

如果我从多个地方拨打readwrite,我不希望每次都重复yield from次;而是把它们放在Connection里面。我的最终目标是将new_conection函数减少到:

@asyncio.coroutine
def new_conection(stream_in, stream_out):
    conn = Connection(stream_in, stream_out)
    print(conn.read(4))

2 个答案:

答案 0 :(得分:5)

由于StreamReader.read is a coroutine,您调用它的唯一选择是a)将其包装在TaskFuture并通过事件循环运行,b)await ing它来自使用async def定义的协同程序,或c)使用yield from将其与来自定义为@asyncio.coroutine装饰的函数的协程一起使用。

由于从事件循环调用Connection.read(通过协程new_connection),您无法重用该事件循环来为{TaskFuture运行{ {1}}:event loops can't be started while they're already running。您要么stop the event loop(灾难性的,可能无法正确执行)或create a new event loop(混乱并且无法使用协程的目的)。这些都不可取,因此StreamReader.read需要是协程或Connection.read函数。

其他两个选项(async协程中的awaitasync def - 装饰函数中的yield from)大致相当。唯一的区别是async def and await were added in Python 3.5,因此对于3.4,使用@asyncio.coroutineyield from是唯一的选项(协程和@asyncio.coroutine在3.4之前不存在,所以其他版本是无关紧要的)。就个人而言,我更喜欢使用asyncioasync def,因为使用await定义协同程序比使用装饰器更清晰,更清晰。

简而言之:让async defConnection.read成为协同程序(使用装饰器或new_connection关键字),然后使用async(或await)在调用其他协程时(yield from中的await conn.read(4)new_connection中的await self.__in.read(n_bytes))。

答案 1 :(得分:1)

我在第620行发现StreamReader source code的一大块实际上是函数使用的完美示例。

在我之前的回答中,我忽略了这样一个事实,即self.__in.read(n_bytes)不仅仅是一个协程(我应该知道它是来自asyncio模块XD),但它产生了一个结果在线。所以它实际上是一个发电机,你需要从它产生。

从源代码中借用这个循环,你的read函数看起来像这样:

def read(self, n_bytes : int = -1):
    data = bytearray() #or whatever object you are looking for
    while 1:
        block = yield from self.__in.read(n_bytes)
        if not block:
            break
        data += block
    return data

因为self.__in.read(n_bytes)是一个生成器,所以你必须继续从它产生,直到它产生一个空结果来表示读取结束。现在你的read函数应该返回数据而不是生成器。您不必从这个版本的conn.read()中获益。