Tutorial de Python async / await

T

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!

 

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias para su correcto funcionamiento. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad