Подтвердить что ты не робот

"async with" в Python 3.4

Документы Getting Started for aiohttp предоставляют следующий пример клиента:

import asyncio
import aiohttp

async def fetch_page(session, url):
    with aiohttp.Timeout(10):
        async with session.get(url) as response:
            assert response.status == 200
            return await response.read()

loop = asyncio.get_event_loop()
with aiohttp.ClientSession(loop=loop) as session:
    content = loop.run_until_complete(
        fetch_page(session, 'http://python.org'))
    print(content)

И они дают следующее примечание пользователям Python 3.4:

Если вы используете Python 3.4, замените ожидание с async def с декоратором @coroutine.

Если я следую этим инструкциям, я получаю:

import aiohttp
import asyncio

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(10):
        async with session.get(url) as response:
            return (yield from response.text())

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    with aiohttp.ClientSession(loop=loop) as session:
        html = loop.run_until_complete(
            fetch(session, 'http://python.org'))
        print(html)

Однако это не будет выполняться, потому что async with не поддерживается в Python 3.4:

$ python3 client.py 
  File "client.py", line 7
    async with session.get(url) as response:
             ^
SyntaxError: invalid syntax

Как я могу преобразовать оператор async with для работы с Python 3.4?

4b9b3361

Ответ 1

Просто не используйте результат session.get() в качестве менеджера контекста; вместо этого используйте его как сопрограмму. Менеджер контекста запроса, который производит session.get(), обычно освобождает запрос при выходе, но и использует response.text(), поэтому вы можете игнорировать это здесь:

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(10):
        response = yield from session.get(url)
        return (yield from response.text())

Возвращенная здесь обертка запроса не имеет необходимых асинхронных методов (__aenter__и __aexit__), они полностью опущены, если не использовать Python 3.5 (см. соответствующий исходный код).

Если у вас есть больше операторов между вызовом session.get() и доступ к response.text(), ожидаемым, вы, вероятно, захотите использовать try:..finally: в любом случае, чтобы освободить соединение; диспетчер контекста выпуска Python 3.5 также закрывает ответ, если возникло исключение. Поскольку здесь нужен yield from response.release(), он не может быть инкапсулирован в диспетчере контекстов до Python 3.4:

import sys

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(10):
        response = yield from session.get(url)
        try:
            # other statements
            return (yield from response.text())
        finally:
            if sys.exc_info()[0] is not None:
                # on exceptions, close the connection altogether
                response.close()
            else:
                yield from response.release()

Ответ 2

aiohttp examples реализовано с использованием синтаксиса 3.4. На основе json client example ваша функция будет:

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(10):
        resp = yield from session.get(url)
        try:
            return (yield from resp.text())
        finally:
            yield from resp.release()

Upd:

Обратите внимание, что решение Martijn будет работать для простых случаев, но может привести к нежелательному поведению в конкретных случаях:

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(5):
        response = yield from session.get(url)

        # Any actions that may lead to error:
        1/0

        return (yield from response.text())

# exception + warning "Unclosed response"

Кроме исключения, вы также получите предупреждение "Unclosed response". Это может привести к утечке соединений в сложном приложении. Вы избежите этой проблемы, если вы вызовете вручную resp.release()/resp.close():

@asyncio.coroutine
def fetch(session, url):
    with aiohttp.Timeout(5):
        resp = yield from session.get(url)
        try:

            # Any actions that may lead to error:
            1/0

            return (yield from resp.text())
        except Exception as e:
            # .close() on exception.
            resp.close()
            raise e
        finally:
            # .release() otherwise to return connection into free connection pool.
            # It ok to release closed response:
            # https://github.com/KeepSafe/aiohttp/blob/master/aiohttp/client_reqrep.py#L664
            yield from resp.release()

# exception only

Я думаю, что лучше следовать официальным примерам (и __aexit__ реализация) и явно вызывать resp.release()/resp.close().