Tutorial de Python async / await

    La programaci贸n asincr贸nica ha ganado mucha tracci贸n en los 煤ltimos a帽os y por una buena raz贸n. Aunque puede ser m谩s dif铆cil que el estilo lineal tradicional, tambi茅n es mucho m谩s eficiente.

    Por ejemplo, en lugar de esperar a que finalice una solicitud HTTP antes de continuar con la ejecuci贸n, con las corrutinas as铆ncronas de Python puede enviar la solicitud y realizar otro trabajo que est谩 esperando en una cola mientras espera que finalice la solicitud HTTP. Puede que sea necesario pensar un poco m谩s para obtener la l贸gica correcta, pero podr谩 manejar mucho m谩s trabajo con menos recursos.

    Incluso entonces, la sintaxis y la ejecuci贸n de funciones asincr贸nicas en lenguajes como Python en realidad no son tan dif铆ciles. Ahora, JavaScript es una historia diferente, pero Python parece ejecutarlo bastante bien.

    La asincronicidad parece ser una de las principales razones por las que Node.js es tan popular para la programaci贸n del lado del servidor. Gran parte del c贸digo que escribimos, especialmente en aplicaciones de E / S pesadas como sitios web, depende de recursos externos. Esto podr铆a ser cualquier cosa, desde una llamada de base de datos remota hasta POSTing o un servicio REST. Tan pronto como solicite cualquiera de estos recursos, su c贸digo estar谩 esperando sin nada que hacer.

    Con la programaci贸n asincr贸nica, permite que su c贸digo maneje otras tareas mientras espera que estos otros recursos respondan.

    Corutinas

    Una funci贸n asincr贸nica en Python generalmente se llama ‘corrutina’, que es solo una funci贸n que usa la async palabra clave o una que est茅 decorada con @asyncio.coroutine. Cualquiera de las siguientes funciones funcionar铆a como una corrutina y son efectivamente equivalentes en tipo:

    import asyncio
    
    async def ping_server(ip):
        pass
    
    @asyncio.coroutine
    def load_file(path):
        pass
    

    Estas son funciones especiales que devuelven objetos de rutina cuando se llaman. Si est谩 familiarizado con las Promesas de JavaScript, entonces puede pensar en este objeto devuelto casi como una Promesa. Llamar a cualquiera de estos no los ejecuta en realidad, sino un corrutina se devuelve el objeto, que luego se puede pasar al bucle de eventos para que se ejecute m谩s adelante.

    En caso de que alguna vez necesite determinar si una funci贸n es una corrutina o no, asyncio proporciona el m茅todo asyncio.iscoroutinefunction(func) que hace exactamente esto por ti. O, si necesita determinar si un objeto devuelto por una funci贸n es un objeto de rutina, puede usar asyncio.iscoroutine(obj) en lugar.

    Rendimiento de

    Hay algunas formas de llamar a una corrutina, una de las cuales es la yield from m茅todo. Esto se introdujo en Python 3.3 y se ha mejorado a煤n m谩s en Python 3.5 en forma de async/await (que veremos m谩s adelante).

    los yield from La expresi贸n se puede usar de la siguiente manera:

    import asyncio
    
    @asyncio.coroutine
    def get_json(client, url):
        file_content = yield from load_file('/Users/scott/data.txt')
    

    Como se puede ver, yield from se utiliza dentro de una funci贸n decorada con @asyncio.coroutine. Si tuvieras que probar y usar yield from fuera de esta funci贸n, obtendr铆a un error de Python como este:

      File "main.py", line 1
        file_content = yield from load_file('/Users/scott/data.txt')
                      ^
    SyntaxError: 'yield' outside function
    

    Para utilizar esta sintaxis, debe estar dentro de otra funci贸n (normalmente con el decorador de corrutinas).

    Async / await

    La sintaxis m谩s nueva y m谩s limpia es utilizar la async/await palabras clave. Introducido en Python 3.5, async se utiliza para declarar una funci贸n como una corrutina, muy similar a lo que @asyncio.coroutine decorador lo hace. Se puede aplicar a la funci贸n coloc谩ndola al principio de la definici贸n:

    async def ping_server(ip):
        # ping code here...
    

    Para llamar realmente a esta funci贸n, usamos await, en vez de yield from, pero de la misma manera:

    async def ping_local():
        return await ping_server('192.168.1.1')
    

    De nuevo, como yield from, no puede usar esto fuera de otra corrutina, de lo contrario obtendr谩 un error de sintaxis.

    En Python 3.5, se admiten ambas formas de llamar a las corrutinas, pero la async/await way est谩 destinado a ser la sintaxis principal.

    Ejecutando el bucle de eventos

    Ninguna de las cosas de rutina que describ铆 anteriormente importar谩 (o funcionar谩) si no sabe c贸mo iniciar y ejecutar un bucle de eventos. El bucle de eventos es el punto central de ejecuci贸n de las funciones asincr贸nicas, por lo que cuando desee ejecutar la corrutina, esto es lo que utilizar谩.

    El bucle de eventos le ofrece bastantes funciones:

    • Registrar, ejecutar y cancelar llamadas retrasadas (funciones asincr贸nicas)
    • Cree transportes de cliente y servidor para la comunicaci贸n
    • Cree subprocesos y transportes para la comunicaci贸n con otro programa
    • Delegar llamadas a funciones a un grupo de subprocesos

    Si bien en realidad hay bastantes configuraciones y tipos de bucles de eventos que puede usar, la mayor铆a de los programas que escriba solo necesitar谩n usar algo como esto para programar una funci贸n:

    import asyncio
    
    async def speak_async():
        print('OMG asynchronicity!')
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(speak_async())
    loop.close()
    

    Las 煤ltimas tres l铆neas son las que nos interesan aqu铆. Comienza obteniendo el bucle de eventos predeterminado (asyncio.get_event_loop()), programar y ejecutar la tarea as铆ncrona y luego cerrar el ciclo cuando el ciclo haya terminado.

    los loop.run_until_complete() La funci贸n en realidad est谩 bloqueando, por lo que no regresar谩 hasta que todos los m茅todos asincr贸nicos est茅n listos. Dado que solo estamos ejecutando esto en un solo hilo, no hay forma de que pueda avanzar mientras el bucle est谩 en progreso.

    Ahora, podr铆a pensar que esto no es muy 煤til, ya que terminamos bloqueando el bucle de eventos de todos modos (en lugar de solo las llamadas IO), pero imag铆nese envolver todo su programa en una funci贸n as铆ncrona, que luego le permitir铆a ejecutar muchos procesos as铆ncronos. solicitudes al mismo tiempo, como en un servidor web.

    Incluso podr铆a romper el bucle de eventos en su propio hilo, dej谩ndolo manejar todas las solicitudes IO largas mientras el hilo principal maneja la l贸gica del programa o la interfaz de usuario.

    Un ejemplo

    Bien, veamos un ejemplo un poco m谩s grande que realmente podemos ejecutar. El siguiente c贸digo es un programa asincr贸nico bastante simple que obtiene JSON de Reddit, analiza el JSON e imprime las publicaciones principales del d铆a de / r / python, / r / programaci贸n y / r / compsci.

    El primer m茅todo mostrado, get_json(), es llamado por get_reddit_top() y simplemente crea una solicitud HTTP GET a la URL apropiada de Reddit. Cuando esto se llama con await, el bucle de eventos puede continuar y dar servicio a otras corrutinas mientras espera que vuelva la respuesta HTTP. Una vez que lo hace, el JSON se devuelve a get_reddit_top(), se analiza y se imprime.

    import signal
    import sys
    import asyncio
    import aiohttp
    import json
    
    loop = asyncio.get_event_loop()
    client = aiohttp.ClientSession(loop=loop)
    
    async def get_json(client, url):
        async with client.get(url) as response:
            assert response.status == 200
            return await response.read()
    
    async def get_reddit_top(subreddit, client):
        data1 = await get_json(client, 'https://www.reddit.com/r/' + subreddit + '/top.json?sort=top&t=day&limit=5')
    
        j = json.loads(data1.decode('utf-8'))
        for i in j['data']['children']:
            score = i['data']['score']
            title = i['data']['title']
            link = i['data']['url']
            print(str(score) + ': ' + title + ' (' + link + ')')
    
        print('DONE:', subreddit + 'n')
    
    def signal_handler(signal, frame):
        loop.stop()
        client.close()
        sys.exit(0)
    
    signal.signal(signal.SIGINT, signal_handler)
    
    asyncio.ensure_future(get_reddit_top('python', client))
    asyncio.ensure_future(get_reddit_top('programming', client))
    asyncio.ensure_future(get_reddit_top('compsci', client))
    loop.run_forever()
    

    Esto es un poco diferente al c贸digo de muestra que mostramos anteriormente. Para que se ejecuten m煤ltiples corrutinas en el bucle de eventos, estamos usando asyncio.ensure_future() y luego ejecutar el ciclo para siempre para procesar todo.

    Para ejecutar esto, deber谩 instalar aiohttp primero, lo que puede hacer con PIP:

    $ pip install aiohttp
    

    Ahora solo aseg煤rese de ejecutarlo con Python 3.5 o superior, y deber铆a obtener un resultado como este:

    $ python main.py
    46: Python async/await Tutorial (http://Pharos.sh.com/python-async-await-tutorial/)
    16: Using game theory (and Python) to explain the dilemma of exchanging gifts. Turns out: giving a gift probably feels better than receiving one... (http://vknight.org/unpeudemath/code/2015/12/15/The-Prisoners-Dilemma-of-Christmas-Gifts/)
    56: Which version of Python do you use? (This is a poll to compare the popularity of Python 2 vs. Python 3) (http://strawpoll.me/6299023)
    DONE: python
    
    71: The Semantics of Version Control - Wouter Swierstra (http://www.staff.science.uu.nl/~swier004/Talks/vc-semantics-15.pdf)
    25: Favorite non-textbook CS books (https://www.reddit.com/r/compsci/comments/3xag9e/favorite_nontextbook_cs_books/)
    13: CompSci Weekend SuperThread (December 18, 2015) (https://www.reddit.com/r/compsci/comments/3xacch/compsci_weekend_superthread_december_18_2015/)
    DONE: compsci
    
    1752: 684.8 TB of data is up for grabs due to publicly exposed MongoDB databases (https://blog.shodan.io/its-still-the-data-stupid/)
    773: Instagram's Million Dollar Bug? (http://exfiltrated.com/research-Instagram-RCE.php)
    387: Amazingly simple explanation of Diffie-Hellman. His channel has tons of amazing videos and only a few views :( thought I would share! (https://www.youtube.com/watch?v=Afyqwc96M1Y)
    DONE: programming
    

    Tenga en cuenta que si ejecuta esto varias veces, el orden en el que se imprimen los datos del subreddit cambia. Esto se debe a que cada una de las llamadas que hacemos libera (rinde) el control del hilo, permitiendo que se procese otra llamada HTTP. El que regrese primero se imprime primero.

    Conclusi贸n

    Aunque la funcionalidad as铆ncrona incorporada de Python no es tan fluida como la de JavaScript, eso no significa que no pueda usarla para aplicaciones interesantes y eficientes. Solo t贸mese 30 minutos para aprender sus entresijos y tendr谩 una idea mucho mejor de c贸mo puede integrar esto en sus propias aplicaciones.

    驴Qu茅 opinas del async / await de Python? 驴C贸mo lo has usado en el pasado? 隆H谩znoslo saber en los comentarios!

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *