web-dev-qa-db-fra.com

"async avec" dans Python 3.4

Les documents de mise en route pour aiohttp donnent l'exemple de client suivant:

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)

Et ils donnent la note suivante pour les utilisateurs de Python 3.4:

Si vous utilisez Python 3.4, veuillez remplacer wait avec yield from et async def par un décorateur @coroutine.

Si je suis ces instructions, j'obtiens:

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)

Cependant, cela ne fonctionnera pas, car async with n'est pas pris en charge dans Python 3.4:

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

Comment traduire le async with instruction pour travailler avec Python 3.4?

21
Imran

N'utilisez simplement pas le résultat de session.get() comme gestionnaire de contexte; utilisez-le directement comme coroutine. Le gestionnaire de contexte de requête que session.get() produit devrait normalement libérer la requête à la sortie, mais tout comme l'utilisation de response.text() , vous pouvez donc ignorer cela ici:

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

L'encapsuleur de demande renvoyé ici n'a pas les méthodes asynchrones requises (__aenter__ Et __aexit__), Ils ont complètement omis lorsqu'ils n'utilisent pas Python 3.5 (voir le - code source pertinent ).

Si vous avez plus d'instructions entre l'appel session.get() et l'accès à response.text() en attente, vous voudrez probablement utiliser un try:..finally: De toute façon pour libérer la connexion; le Python 3.5 également ferme la réponse si une exception s'est produite. Parce qu'une yield from response.release() est nécessaire ici, cela ne peut pas être encapsulé dans un gestionnaire de contexte avant 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()
17
Martijn Pieters

aiohttpexemples implémenté en utilisant la syntaxe 3.4. Basé sur exemple client json votre fonction serait:

@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()

Mise à jour:

Notez que la solution de Martijn fonctionnerait pour des cas simples, mais peut conduire à un comportement indésirable dans des cas spécifiques:

@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"

Outre l'exception, vous obtiendrez également un avertissement "Réponse non divulguée". Cela peut entraîner une fuite de connexions dans une application complexe. Vous éviterez ce problème si vous appelez manuellement 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's ok to release closed response:
            # https://github.com/KeepSafe/aiohttp/blob/master/aiohttp/client_reqrep.py#L664
            yield from resp.release()

# exception only

Je pense qu'il vaut mieux suivre les exemples officiels (et __aexit__implémentation ) et appeler explicitement resp.release()/resp.close().

5