paint-brush
Agregar un operador de tubería a Pythonpor@popa.bogdanp
32,353 lecturas
32,353 lecturas

Agregar un operador de tubería a Python

por Bogdan Popa2018/08/12
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

El otro día, estaba mirando un código de Python que estaba compuesto por una secuencia de llamadas a funciones profundamente anidadas y un pensamiento inmediatamente subió a la pila del cerebro:

People Mentioned

Mention Thumbnail

Company Mentioned

Mention Thumbnail
featured image - Agregar un operador de tubería a Python
Bogdan Popa HackerNoon profile picture

O, "cosas que nunca deberías hacer en producción"

El otro día, estaba mirando un código de Python que estaba compuesto por una secuencia de llamadas a funciones profundamente anidadas y un pensamiento inmediatamente subió a la pila del cerebro:

¿No sería genial si Python tuviera un operador similar al operador de tubería de Elixir?

¿Operador de tuberías?

Para aquellos de ustedes que no están familiarizados con Elixir, el operador de canalización |> se puede usar para convertir un código que se ve así:

Llamadas a funciones anidadas.

En un código que se ve así:

¡Refrescante!

En esencia, el operador de tubería toma la expresión de su lado izquierdo y la mueve a la posición del primer argumento de la expresión de su lado derecho, asumiendo que la expresión de la derecha es una llamada de función.

Haciéndolo en Python

Aunque no es posible agregar un operador de tubería real al lenguaje sin cambiar el intérprete de Python, ¡no hay nada que nos impida reutilizar un operador existente! Eso es exactamente lo que me propuse hacer.

Tomando prestado de los shells, pensé que el operador "bit a bit o" (también conocido como "tubería") sería una buena opción para este tipo de funcionalidad:

La forma más sencilla que se me ocurrió para reutilizar el operador de tubería fue reescribir funciones usando un decorador. El módulo ast incorporado de Python hace que esto sea particularmente fácil.

Todo lo que tenía que hacer era

  • obtener el código fuente de una función (usando inspect.getsource ),
  • conviértalo en un árbol de sintaxis abstracto pasándolo a ast.parse ,
  • camine por el árbol y transforme cualquier ocurrencia del operador de tubería de acuerdo con las reglas que describí al final de la última sección y,
  • finalmente, compile y devuelva el objeto de función reescrito.

Eso suena mucho más complicado de lo que resulta ser en la práctica.

El módulo ast proporciona una clase llamada NodeTransformer que implementa el patrón de visitante: su método de visit realiza una búsqueda en profundidad a través de un AST y llama a cualquier método declarado de la forma visit_NODETYPE en sí mismo para cada nodo en el árbol. Como su nombre lo indica, puede usar un transformador de nodos para visitar y manipular los nodos de un AST:

Cada vez que el transformador encuentra un operador binario, recurre para que las transformaciones que deben realizarse en sus nodos izquierdo y derecho se realicen primero, luego verifica si el operador actual es "bit a bit o".

Si el operador del nodo actual es "bit a bit o" y si el lado derecho es un nodo de llamada de función, entonces inserta el lado izquierdo en la posición del primer argumento de la llamada de función y luego devuelve el nodo de la derecha lado, reemplazando el nodo del operador binario con el nodo de llamada en el árbol.

El transformador también se activa cuando ve una definición de función para que pueda eliminar el decorador enable_threadop . Esto tendrá sentido una vez que eches un vistazo al decorador en sí:

El decorador toma una función como argumento, toma su código fuente y elimina cualquier sangría (¡esto es importante! De lo contrario, los métodos de decoración de clase dan como resultado un SyntaxError ), analiza el código y transforma el AST y, finalmente, compila y ejecuta la función. definición, devolviendo el objeto de función resultante.

Si el transformador no hubiera eliminado el decorador del árbol final, tendríamos un bucle infinito en nuestras manos porque se llamaría a enable_threadop una y otra vez cuando se ejecuta la definición de la función (línea 13).

Con todo eso en su lugar, el decorador enable_threadop se puede usar para cambiar selectivamente el comportamiento del operador de tubería:

Si ese no es un ejemplo realista, ¡no sé qué lo es!

Limitaciones

Como era de esperar, hay un par de limitaciones con este enfoque.

En primer lugar, inspect.getsource el código fuente de las funciones del sistema de archivos, lo que significa que el decorador no funcionará en el intérprete de Python.

En segundo lugar, el transformador requiere que el lado derecho del operador de tubería sea una llamada de función.

¡Es hora de usarlo en producción!

¡Vaya, más despacio! ¡Este es solo un pequeño experimento genial y definitivamente no es algo que deba infligir a sus compañeros de trabajo!

Dicho esto, si quieres jugar con él, puedes encontrar el código completo (¡50 líneas con comentarios!) en GitHub .

¡Gracias por leer! Si te ha gustado el artículo, ¡dale un aplauso! También puede encontrarme en mi sitio web en https://defn.io , en GitHub y Twitter .


Bogdan Popa (@bogdanp) | Twitter _Los últimos Tweets de Bogdan Popa (@bogdanp). Programador, creador de https://t.co/FFd6cPhKk5 y…_twitter.com