Las corrutinas son básicamente funciones cuya ejecución se puede pausar/suspender en un punto en particular, y luego podemos reanudar la ejecución desde ese mismo punto más tarde cuando queramos.

Necesitamos un mecanismo, o para ser más precisos, una palabra clave, mediante el cual podamos insertar un punto de control y decirle al programa que queremos pausar la ejecución de la función aquí y devolver el control al punto desde el que llamó. Reanudaremos la ejecución cuando queramos.

En Python, podemos pausar la ejecución de una función usando la palabra clave yield.

Así que aquí es donde las cosas se ponen interesantes:

  • Podemos pensar en una corrutina como una función que tiene uno o más puntos de control donde la ejecución se pausará y el control se devolverá al punto desde el que se llamó.
  • En esencia, una corrutina es una función dividida en muchas partes, y podemos ejecutar cada parte de una corrutina a medida que ejecutamos cada iteración de un bucle for usando la función next.

Este es un ejemplo básico:

OUTPUT :<class 'generator'>
Function Starts
Function Ends

De la salida, notamos algunas cosas:

  • Primero, necesitamos llamar a la corrutina / función que nos dará un objeto generador.
  • Ese objeto generador se comportará de manera similar a un iterador, pero en el caso de un iterador, estamos atravesando un iterable. Con un generador, estamos ejecutando partes de la corrutina.
  • Al igual que se lanza una excepción StopIteration y se captura detrás de las escenas de un bucle for, lo mismo sucede en este caso cuando se ejecuta la última parte de la corrutina.

Ahora esta pausa de la función es muy interesante y abre algunas posibilidades:

  • Cuando la función está en pausa, no hacemos nada, que es el caso que acabamos de ver.
  • Supongamos que una variable se modifica varias veces en una función y queremos el valor de esa variable en particular en un punto de control determinado. Luego, cuando pausamos esa función en ese punto de control en particular, devuelve el valor de esa variable.

Veamos un ejemplo:

OUTPUT :Function Part 1
5
Function part 2
12
Function part 3

Aquí, el valor de x es devuelto por yield en diferentes puntos de control, ya que la ejecución de la función se ha detenido.

Siempre que estemos ejecutando la última parte de la función y no quede ningún rendimiento en la función, después de ejecutar esa última parte, se generará una excepción StopIteration.

Al igual que cuando un iterador intenta ejecutar la siguiente función, pero no quedan más elementos en el iterable, también genera la excepción StopIteration.

  • Supongamos que queremos enviar un valor (que puede ser una constante o variable) en un punto de control determinado (es decir, en un cierto estado de una función). También podemos hacerlo usando la palabra clave yield. Cuando queramos enviar un valor, usaremos la función send en lugar de next.

Veamos un ejemplo:

OUTPUT :Function part 1
6
Function part 2
12
Function part 3

La razón por la que usamos next antes de usar send es que solo podemos usar send cuando estamos en el punto de control yield, y yield está en el lado derecho de la expresión. Así que para llegar a ese primer yield, tenemos que usar la función next.

Ahora aquí viene una interesante aplicación de corrutinas. Supongamos que queremos cambiar de un lado a otro entre dos funciones como lo hacemos en multihilo. En multihilo, hasta que el sistema operativo encuentre un interrupt, seguirá ejecutándose. En este caso, podemos cambiar cuando queramos.

Veamos un ejemplo:

OUTPUT :Function 1 part 1
Function 2 part 1
Function 1 part 2
Function 1 part 3
Function 2 part 2
Function 2 part 3
Function 2 part 4
Function 1 part 4
Function 1 part 5

En este ejemplo, podemos ver que podemos cambiar entre corrutinas cuando queramos.

Así que si escribimos nuestro propio programador personalizado que maneja el cambio entre varias corrutinas, podemos lograr con un solo subproceso lo que hacemos con el subproceso múltiple.

Las corrutinas tienen muchas aplicaciones, como la concurrencia, y también se pueden implementar otros patrones de programación, como Productor-Consumidor o Emisor-Receptor en la programación de red. Los exploraré en próximos artículos.

Las corrutinas también son los bloques de construcción de muchos marcos como asyncio, twisted, aiohttp. También se pueden encadenar para hacer tuberías y resolver problemas.