¡No olvides que Python es dinámico!

Remember, Python is dynamic!

PROGRAMACIÓN EN PYTHON

Cada vez más chequeos estáticos y dinámicos… ¿Es hacia donde queremos que se dirija Python?

¿Hacia dónde se dirige Python? Foto de Jamie Templeton en Unsplash

Python es un lenguaje dinámico. Sin embargo, en los últimos años se ha prestado cada vez más atención a la comprobación de tipos estáticos. Esto, a su vez, lleva a un mayor interés en la comprobación de tipos durante la ejecución. ¿Hasta dónde llegaremos con esto? En este artículo, recordaremos por qué Python fue, no hace mucho tiempo, considerado un potente lenguaje de programación tipado dinámicamente.

¿Lo sigue siendo?

La fortaleza de Python siempre ha radicado en su simplicidad, que en parte se debió a la tipificación dinámica, no solo porque podemos escribir código Python en REPL, sino también por las siguientes razones:

  • Puede cambiar fácilmente el tipo de una variable en todo el programa.
  • No tiene que definir el tipo de una variable.
  • El código es (o puede ser) fácil de leer y entender.
  • A veces, puede usar un par de líneas de código para implementar incluso un algoritmo bastante complejo. Un lenguaje tipado estáticamente generalmente necesita muchas más líneas de código (o al menos más).

Ciertamente, la tipificación dinámica no viene sin costo. El primero es una disminución del rendimiento, algo que todos conocemos. Esta disminución se debe al hecho de que los tipos -que no están declarados- deben ser comprobados en tiempo de ejecución (lo hace Python). Otro costo es el aumento del riesgo de errores en tiempo de ejecución: como los tipos no se comprueban en tiempo de compilación sino en tiempo de ejecución, los errores relacionados se producen durante la ejecución del programa en lugar de durante la compilación.

Necesitamos recordar que Python ofrece un conjunto de herramientas para manejar su disminución del rendimiento:

¡La velocidad de Python: no es tan mala!

Escucho todo el tiempo que Python es demasiado lento. ¿Es así?

Zepes.com

Durante años, los Pythonistas estuvieron orgullosos y felices de que Python fuera un lenguaje tipado dinámicamente. Claro, aquellos que no les gustaba Python afirmaban que era un mal lenguaje… ¿Qué puedo decir? Cada uno puede pensar lo que quiera; la programación es un universo libre.

La programación es un universo libre.

Por algún tiempo, sin embargo, Python se ha ido moviendo hacia el lado estático de la programación. El aspecto más importante de este movimiento son las pistas de tipos. Claro, son opcionales, pero la mayoría de los grandes proyectos escritos en la actualidad implementan pistas de tipos. Escuchará más a menudo que en un proyecto serio debe sugerir tipos que no tiene que hacerlo, por no mencionar que no debería. Prepárese para escuchar algo así: “Claro, las pistas de tipos son opcionales, así que para prototipos y scripts cortos no necesita usarlas, pero para proyectos grandes… bueno, no hay otra opción que sugerir tipos”. Lo he escuchado más de una y más de dos veces.

Toda esta situación plantea la siguiente pregunta: ¿Realmente necesitamos todas esas pistas de tipos, comprobadores de tipos estáticos y comprobadores de tipos durante la ejecución?

No voy a responder a esta pregunta. Esto se debe principalmente a que estoy lejos de ser una de esas personas que piensan que saben todo sobre… bueno, sobre todo, o casi todo. Pero espero invitarlos a pensar en esto ustedes mismos.

Sin embargo, recordaré -y me recordaré a mí mismo- que la tipificación dinámica de Python, también llamada tipificación pato, está detrás del éxito de este lenguaje. A continuación se muestra la explicación popular de cómo funciona la tipificación pato:

Si camina como un pato y grazna como un pato, entonces debe ser un pato.

La tipificación pato puede ser muy poderosa sin pistas de tipos y comprobación de tipos durante la ejecución. Les mostraré esto en ejemplos muy simples, y sin más preámbulos, saltemos a este ejemplo simple:

Capturando errores mediante comprobación de tipos durante la ejecución. Imagen del autor

Aquí, estamos comprobando los tipos de x e y, y ambos deben ser cadenas (str). Tenga en cuenta que de esta manera, estamos comprobando si lo que proporcionamos al método str.join() es un tuple[str, str]. Ciertamente, no tenemos que comprobar si este método recibe una tupla, ya que la estamos creando nosotros mismos; es suficiente con comprobar los tipos de x e y. Cuando alguno de ellos no es una cadena, la función generará un TypeError con un mensaje simple: "¡Proporcione una cadena!".

Genial, ¿verdad? Estamos seguros de que la función se ejecutará solo en valores de tipos correctos. Si no es así, veremos un mensaje personalizado. También podríamos usar un error personalizado:

¿Deberíamos usar excepciones personalizadas en Python?

Python tiene tantas excepciones integradas que rara vez necesitamos crear y usar excepciones personalizadas. ¿O sí?

towardsdatascience.com

Ahora, eliminemos la comprobación de tipos y veamos cómo funciona la función:

Capturando errores sin comprobación de tipos en tiempo de ejecución; el mensaje no es personalizado sino integrado. Imagen del autor

Ja. Parece estar funcionando de manera bastante similar… quiero decir, una excepción se genera básicamente en el mismo lugar, así que no estamos arriesgando nada. Entonces…

De hecho, aquí la función foo_no_check() utiliza el duck typing, que utiliza un concepto de tipos implícitos. En este mismo ejemplo, el método str.join() asume que toma una tupla de cadenas, por lo que tanto x como y tienen que ser cadenas, y si no lo son, el tipo implícito para tuple[str, str] no se ha implementado. Por lo tanto, se genera el error.

Puede decir: “¡Pero eh! ¡Mira el mensaje! Antes, podíamos usar un mensaje personalizado, ¡y ahora no podemos!”

¿Realmente no podemos? Mira:

Capturando errores sin comprobación de tipos en tiempo de ejecución; el mensaje de error está personalizado. Imagen del autor

Ahora podemos ver ambos mensajes: el integrado (sequence item 1: expected str instance, in found) y el personalizado (¡Proporcione una cadena!).

Rendimiento

Podrías preguntar: ¿Cuál es la diferencia? Así que, compruebo los tipos. ¿Cuál es el problema?

Bueno, hay bastante diferencia: el rendimiento. Veamos las tres versiones de la función usando el paquete perftester:

Probando el rendimiento de las funciones de Python de manera fácil: perftester

Puedes usar perftester para probar el rendimiento de las funciones de Python de manera fácil

towardsdatascience.com

Aquí están las pruebas:

Pruebas realizadas con el paquete perftester. Imagen del autor

Para todas las pruebas en este artículo, usé Python 3.11 en una máquina con Windows 10, en WSL 1, 32GB de RAM y cuatro núcleos físicos (ocho lógicos).

En la segunda línea, establezco el número predeterminado de experimentos en 10, y dentro de cada ejecución, cada función se ejecuta cien millones de veces. Tomamos el mejor de los diez ejecuciones, y reportamos el tiempo promedio en segundos.

La función foo(), la que tiene las comprobaciones de tipo en tiempo de ejecución, es significativamente más lenta que las otras dos. La función foo_no_check() es la más rápida, aunque foo_no_check_tryexcept() es solo un poco más lenta.

¿Conclusión? Las comprobaciones de tipo en tiempo de ejecución son costosas.

Puedes decir: “¿Qué? ¿Me estás tomando el pelo? ¿Costosas? ¡Es solo una parte menor de un segundo! ¡Ni siquiera un microsegundo!”

En efecto. No es mucho. Pero esta es una función muy simple con solo dos comprobaciones. Ahora imagina una gran base de código, con muchas clases, métodos y funciones, y muchas comprobaciones de tipo en tiempo de ejecución. A veces, esto puede significar una disminución significativa en el rendimiento.

Las comprobaciones de tipo en tiempo de ejecución son costosas.

Conclusión

Cuando lees sobre el tipado pato, generalmente verás ejemplos con gatos que maúllan, perros que no y vacas que mugen. Cuando escuchas a un animal maullando, no es ni un perro ni una vaca, es un gato. Pero no un tigre. Decidí usar un ejemplo atípico, y espero que haya sido lo suficientemente claro para que veas las fortalezas del tipado pato.

Como ves, el manejo de excepciones de Python hace un gran trabajo en la comprobación de tipos en tiempo de ejecución. Puedes ayudarlo agregando comprobaciones de tipo adicionales cuando sea necesario, pero siempre recuerda que agregarán algo de tiempo de sobrecarga.

¿Conclusión? Python tiene excelentes herramientas de manejo de excepciones que funcionan bastante bien. A menudo, no tenemos que usar comprobaciones de tipo en tiempo de ejecución en absoluto. Sin embargo, a veces podemos necesitarlo. Cuando dos tipos tienen interfaces similares, el tipado pato puede fallar.

Por ejemplo, imagina que quieres sumar dos números (x + y), pero el usuario proporcionó dos cadenas. Esto no significará un error porque puedes sumar dos cadenas. En tales casos, es posible que debas agregar una comprobación de tipo en tiempo de ejecución si no quieres que el programa continúe con estos valores incorrectos. Tarde o temprano, de todos modos puede fallar, por lo que la pregunta es si quieres que el programa continúe hasta entonces o no. Si es así, puedes correr el riesgo de que se genere una excepción mucho más tarde, por lo que agregar una comprobación de tipo puede ayudar a ahorrar tiempo. Además, generar la excepción más tarde en el flujo del programa puede significar dificultades para encontrar la verdadera causa del error.

En general, no quiero decirte que nunca debes usar comprobaciones de tipo en tiempo de ejecución. Pero a menudo, se agregan tales comprobaciones cuando no son necesarias.

Espero haber intrigado e inspirado. Hoy, todo lo que quería lograr es esto. Por favor, comparte tus pensamientos en los comentarios. Dinos si usarías o no comprobaciones de tipo en tiempo de ejecución en situaciones tan simples.

No he hablado sobre la comprobación estática y el tipado ganso. He escrito varios artículos sobre comprobación estática e indicaciones de tipo:

Indicaciones de tipo de Python: ¿Amigo, enemigo o solo un dolor de cabeza?

La popularidad de las indicaciones de tipo está aumentando en la comunidad de Python. ¿Adónde nos llevará esto? ¿Qué podemos hacer para usarlo…

betterprogramming.pub

Indicaciones de tipo de Python: compatibilidad con el tipado pato y coherencia con él

No tienes que indicar int cuando estás indicando float, o namedtuple cuando estás indicando tuple. ¿Por qué?

towardsdatascience.com

Indicaciones de tipo de Python: de los alias de tipo a las variables de tipo y los nuevos tipos

Mira los alias de tipo, las variables de tipo y los nuevos tipos en acción

towardsdatascience.com

Aún no he escrito sobre el tipado ganso, pero tarde o temprano llegará el momento.

¿Sigue siendo Python considerado un lenguaje poderoso de tipado dinámico? Para ser honestos, no lo sé. Tanta atención en Python se centra en la verificación estática y en tiempo de ejecución que temo que muchos hayan olvidado que el verdadero poder de Python radica en cosas completamente diferentes: su simplicidad, su legibilidad -y sí, el duck typing.

He oído, sin embargo, a Pythonistas experimentados expresar su tristeza porque Python no es lo que fue hace no mucho tiempo. Algunos de ellos decidieron pasar de Python a otros lenguajes, afirmando que “si quiero usar un lenguaje en el que tenga que definir tipos para variables, ¡Python sería mi última elección!” Tiene todo el sentido. Los lenguajes de tipado estático pueden ser mucho más rápidos que Python -y aún así, pueden ser bastante simples y legibles, como Go. Pero Python… Python ofrece duck typing simple y poderoso… Duck typing que muchos parecen olvidar.

A mí me encantan tanto Python como Go. Go es de tipado estático, y es una de las cosas que lo hacen visiblemente más rápido que Python. Pero, ¿sabes qué? El código tipado estáticamente de Go es, para mí, a menudo mucho más fácil de leer y entender que el código de Python con sugerencias de tipos.

Cuando veo verificaciones en tiempo de ejecución por todo el código, me siento cansado y desanimado. No es para lo que se creó Python. Sí, las sugerencias de tipos pueden ser bastante útiles, pero solo cuando se usan correctamente y no se abusan de ellas. Si se abusan, pueden convertirse en una carga.

Cuando veo verificaciones en tiempo de ejecución por todo el código, me siento cansado y desanimado. No es para lo que se creó Python.

Gracias por leer. Si disfrutaste de este artículo, también puedes disfrutar de otros artículos que escribí; los verás aquí. Y si quieres unirte a Zepes, por favor usa mi enlace de referencia a continuación:

Lee todas las historias de Marcin Kozak (y de miles de otros escritores en Zepes). Tu cuota de membresía apoya directamente…

Zepes.com

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

No es el Vader que piensas 3D VADER es un modelo de IA que difunde modelos 3D

La generación de imágenes nunca ha sido tan fácil. Con el surgimiento de los modelos de IA generativos, el proceso se...

Inteligencia Artificial

Construye y entrena modelos de visión por computadora para detectar posiciones de autos en imágenes utilizando Amazon SageMaker y Amazon Rekognition

La visión por computadora (CV) es una de las aplicaciones más comunes del aprendizaje automático (ML) y el aprendizaj...

Inteligencia Artificial

OpenAI presenta Super Alignment Abriendo el camino para una IA segura y alineada

OpenAI Introducing Super Alignment development offers enormous promise for humanity. It has the ability to address so...

Inteligencia Artificial

Mejora el rendimiento de la inferencia para LLMs con los nuevos contenedores de Amazon SageMaker

Hoy, Amazon SageMaker lanza una nueva versión (0.25.0) de los Contenedores de Aprendizaje Profundo (DLC) para Inferen...

Inteligencia Artificial

Desatando el potencial de la IA El auge de las GPU en la nube

Ingresa a las GPUs en la nube, una solución escalable, rentable e inclusiva para los desafíos computacionales complej...

Inteligencia Artificial

Introducción a Numpy y Pandas

Una introducción sobre cómo utilizar Numpy y Pandas para cálculos numéricos y manipulación de datos en Python.