"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?
Ответы
Ответ 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()
.