Python – Y, finalmente, finally

Objetivo: presentar la cláusula finally en el manejo de excepciones en Python.

Imaginemos, por un momento, que soy un perverso programador que, bajo el aspecto de un aparente dócil y simple programa esconde en él una intención oculta. Supongamos que, aprovechándome del deseo de todo el mundo de disponer de una aplicación que permita calcular la división de dos números enteros, intento colar un sutil mensaje subliminal al ejecutarla:

dividendo = int(input('Introduce el dividendo: '))
divisor = int(input('Introduce el divisor: '))
print('El cociente de la división de ambos números es', dividendo//divisor)
print("Viva el Betis manque pierda")

>>> 
Introduce el dividendo: 10
Introduce el divisor: 2
El cociente de la división de ambos números es 5
Viva el Betis manque pierda 

Impresionante, pero no tanto…

Si por un casual introducimos como divisor un cero, la división no podrá realizarse y el programa abortará, muriendo con él también nuestras oscuras pretensiones:

>>> 
Introduce el dividendo: 10
Introduce el divisor: 0
Traceback (most recent call last):
  File "C:/Python33/pruebas.py", line 3, in <module>
    print('El cociente de la división de ambos números es', dividendo//divisor)
ZeroDivisionError: integer division or modulo by zero

En los artículos anteriores presentamos la captura de excepciones en Python, de modo que tenemos recursos para solucionar esto:

try:
    dividendo = int(input('Introduce el dividendo: '))
    divisor = int(input('Introduce el divisor: '))
    print('El cociente de la división de ambos números es', dividendo//divisor)
except:
    print("No ha podido realizarse la operación")
    
print("Viva el Betis manque pierda")

>>> 
Introduce el dividendo: 10
Introduce el divisor: 0
No ha podido realizarse la operación
Viva el Betis manque pierda

Desde luego, esto funciona. La operación de división ha sido protegida con un try/except que, tal como está planteado, intercepta cualquier excepción que pudiera ocurrir. He introducido en el bloque try las dos sentencias input, no sólo la división, pues también ocurriría una excepción si se facilitase un valor no numérico cuando int() intentara la conversión a entero.

El código no fallará ante una eventual división por cero (ZeroDivisionError), la introducción de letras en vez de números (ValueError), o una interrupción con Ctrl-C (KeyboardInterrupt).

Pero no me convence. Quiero emplear mi propio manejador ante una división por cero o la introducción de letras, pero quiero respetar el gestor por omisión que trae Python (el mensaje de error clásico tras abortar el programa) para poder finalizar la ejecución con Ctrl-C si así lo deseo.

Hemos aprendido a interceptar selectivamente las excepciones también:

try:
    dividendo = int(input('Introduce el dividendo: '))
    divisor = int(input('Introduce el divisor: '))
    print('El cociente de la división de ambos números es', dividendo//divisor)
except (ZeroDivisionError, ValueError):
    print("No ha podido realizarse la operación")
    
print("Viva el Betis manque pierda")

Ahora, si dividimos entre cero, salvamos el error y también nuestro mensaje subliminal:

>>> 
Introduce el dividendo: 10
Introduce el divisor: 0
No ha podido realizarse la operación
Viva el Betis manque pierda

Lo mismo si introducimos valores no numéricos:

>>> 
Introduce el dividendo: GOL!!!
No ha podido realizarse la operación
Viva el Betis manque pierda

Pero nuestro gozo en un pozo si abortamos con Ctrl-C:

>>> 
Introduce el dividendo: 
Traceback (most recent call last):
  File "C:/Python33/pruebas.py", line 2, in <module>
    dividendo = int(input('Introduce el dividendo: '))
KeyboardInterrupt

Se rompe la ejecución del programa y, con ello, el Betis pierde.

Para seguir insuflándole ánimos, Python dispone de la claúsula finally en el bloque try. El código que se incluya en ella, apúntate esto bien, se ejecutará siempre, suceda o no suceda una excepción.

En su versión más simple, finally no necesita que haya ningún except:

try:
    dividendo = int(input('Introduce el dividendo: '))
    divisor = int(input('Introduce el divisor: '))
    print('El cociente de la división de ambos números es', dividendo//divisor)
finally:
    print("Viva el Betis manque pierda")

Ahora, pase lo que pase dentro del try, la instrucción del finally se ejecutará siempre. Tanto si sucede algo bueno:

>>> 
Introduce el dividendo: 10
Introduce el divisor: 2
El cociente de la división de ambos números es 5
Viva el Betis manque pierda

Como si no:

>>> 
Introduce el dividendo: 10
Introduce el divisor: 0
Viva el Betis manque pierda
Traceback (most recent call last):
  File "C:/Python33/pruebas.py", line 4, in <module>
    print('El cociente de la división de ambos números es', dividendo//divisor)
ZeroDivisionError: integer division or modulo by zero

Se ejecuta el print del finally y, a continuación, el manejador por omisión se ocupa del tratamiento de la excepción, abortando el programa e informando por qué.

Podemos combinar finally con except también:

try:
    dividendo = int(input('Introduce el dividendo: '))
    divisor = int(input('Introduce el divisor: '))
    print('El cociente de la división de ambos números es', dividendo//divisor)
except (ZeroDivisionError, ValueError):
    print("No ha podido realizarse la operación")
finally:
    print("Viva el Betis manque pierda")

En este caso utilizamos nuestro propio gestor ante una división por cero o la introducción de valores no numéricos, pero mantenemos el que trae por defecto Python para cualquier otra excepción, lo que nos permite abortar el programa con Ctrl- C. El código dentro de la claúsula finally se ejecuta siempre, suceda o no suceda una excepción:

>>> 
Introduce el dividendo: 10
Introduce el divisor: 4
El cociente de la división de ambos números es 2
Viva el Betis manque pierda
>>> ================================ RESTART ================================
>>> 
Introduce el dividendo: 
Viva el Betis manque pierda
Traceback (most recent call last):
  File "C:/Python33/pruebas.py", line 2, in <module>
    dividendo = int(input('Introduce el dividendo: '))
KeyboardInterrupt

El uso típico de la claúsula finally es la realización de tareas de limpieza para que no queden flecos en caso de que se produzca una excepción. Piensa, por ejemplo, en el gesto de cerrar un fichero una vez ha sido abierto para asegurarnos de que, suceda lo que suceda mientras estemos procesando el fichero, siempre quedará perfectamente cerrado.

Dedicaremos el próximo artículo, a modo de resumen, a esquematizar lo que hemos tratado hasta ahora en estos cuatro artículos dedicados a la gestión de excepciones en Python.

Nota: no soy futbolero, ni tengo preferencia por ningún equipo sobre otro, pero sí que admito un cariño y especial admiración por la afición del Betis, tan incondicional, fiel y estoica.

Javier Montero Gabarró


Python – Y, finalmente, finally


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – Sin excepciones, what else?

Objetivo: utilización de la claúsula ELSE en la gestión de excepciones en Python.

En el artículo anterior aprendimos a capturar selectivamente excepciones y escribimos código realmente robusto que realizaba una división entera a prueba de bombas.

El programa era capaz de responder a cualquier excepción fatal. Observa las respuestas personalizadas ante una división por cero o la introducción de un valor no numérico, así como la respuesta genérica ante cualquier otra eventualidad (como el intento de abortar el programa con Ctrl-C).

while True:
    try:
        dividendo = int(input('Introduce el dividendo: '))
        divisor = int(input('Introduce el divisor: '))
        print('El cociente de la división de ambos números es', dividendo//divisor)
    except ZeroDivisionError:
        print('¡No, no, la división por cero no está permitida')
    except ValueError:
        print('¡Ojo, presta atención e introduce sólo números!')
    except:
        print('No sé qué has hecho, pero vuelve a intentarlo')

Pero ahora voy a plantearte un sencillo ejercicio. Quiero que modifiques el programa de modo que, cada vez que el código se ejecute libre de excepciones, aparezca un mensaje indicándolo.

Obviamente, lo siguiente no sirve:

while True:
    try:
        dividendo = int(input('Introduce el dividendo: '))
        divisor = int(input('Introduce el divisor: '))
        print('El cociente de la división de ambos números es', dividendo//divisor)
    except ZeroDivisionError:
        print('¡No, no, la división por cero no está permitida')
    except ValueError:
        print('¡Ojo, presta atención e introduce sólo números!')
    except:
        print('No sé qué has hecho, pero vuelve a intentarlo')
    print("Todo ha ido como la seda")

El mensaje aparece en cada iteración, haya o no sucedido una excepción:

>>> 
Introduce el dividendo: 10
Introduce el divisor: 4
El cociente de la división de ambos números es 2
Todo ha ido como la seda
Introduce el dividendo: 10
Introduce el divisor: 0
¡No, no, la división por cero no está permitida
Todo ha ido como la seda
Introduce el dividendo: 

Es un problema fácil de resolver, no obstante. Podemos hacer uso de una variable booleana para alertar de que se ha producido una excepción:

while True:
    try:
        sin_excepciones = True
        dividendo = int(input('Introduce el dividendo: '))
        divisor = int(input('Introduce el divisor: '))
        print('El cociente de la división de ambos números es', dividendo//divisor)
    except ZeroDivisionError:
        print('¡No, no, la división por cero no está permitida')
        sin_excepciones = False
    except ValueError:
        print('¡Ojo, presta atención e introduce sólo números!')
        sin_excepciones = False
    except:
        print('No sé qué has hecho, pero vuelve a intentarlo')
        sin_excepciones = False
    if sin_excepciones:
        print("Todo ha ido como la seda")

Eso es lo que haríamos en otros lenguajes como Java o C++ (pero no en C, que ni siquiera maneja excepciones). Como vemos, ahora el mensaje aparece exclusivamente sólo si el código ha transcurrido libre de tropiezos:

>>> 
Introduce el dividendo: 10
Introduce el divisor: 0
¡No, no, la división por cero no está permitida
Introduce el dividendo: 10
Introduce el divisor: 4
El cociente de la división de ambos números es 2
Todo ha ido como la seda
Introduce el dividendo: 

Pero esto no es Java ni C++, sino Python…

La sentencia TRY admite una claúsula ELSE para introducir en ella el código que queremos que se ejecute siempre y cuando no haya sucedido ninguna excepción.

while True:
    try:
        dividendo = int(input('Introduce el dividendo: '))
        divisor = int(input('Introduce el divisor: '))
        print('El cociente de la división de ambos números es', dividendo//divisor)
    except ZeroDivisionError:
        print('¡No, no, la división por cero no está permitida')
    except ValueError:
        print('¡Ojo, presta atención e introduce sólo números!')
    except:
        print('No sé qué has hecho, pero vuelve a intentarlo')
    else:
        print("Todo ha ido como la seda")

Y ahora sí…

>>> 
Introduce el dividendo: 10
Introduce el divisor: 0
¡No, no, la división por cero no está permitida
Introduce el dividendo: 10
Introduce el divisor: 4
El cociente de la división de ambos números es 2
Todo ha ido como la seda
Introduce el dividendo: 

Así de sencillo.

Seguro que George Clooney diría, si supiera programar: Python, what else?

Javier Montero Gabarró


Python – Sin excepciones, what else?


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – Captura selectiva de excepciones

Objetivo: mostrar cómo capturar selectivamente las excepciones en Python dependiendo de su tipo.

En el artículo anterior introdujimos el concepto de excepción en Python y aprendimos a proteger a prueba de bombas un determinado bloque de código utilizando la estructura try/except. El bloque a proteger lo recogíamos en la sección try y, si algo inesperado (una excepción) ocurría dentro de él, las instrucciones contenidas en la sección except se ocupaban de su tratamiento.

Recuperemos nuestra robusta división entera:

while True:
    try:
        dividendo = int(input('Introduce el dividendo: '))
        divisor = int(input('Introduce el divisor: '))
        print('El cociente de la división de ambos números es', dividendo//divisor)
    except:
        print('Eso no ha estado bien, pero no pasa nada')
        print('Vuelve a intentarlo')

Si y sólo si sucede una excepción dentro de la sección try, el bloque de la sección except será ejecutado.

En particular, el código está vacunado ante excepciones como, por ejemplo:

ZeroDivisionError: si introducimos un cero como divisor, la división dejará de existir.

ValueError: si en lugar de introducir números escribimos caracteres, la función int() no podrá realizar la conversión.

KeyboardInterrupt: si tecleamos Ctrl-C el programa será abortado.

Pero, ¿y si queremos controlar la captura de excepciones de un modo más selectivo? Por ejemplo, ¿cómo haríamos si sólo queremos dejar protegido el código ante un eventual ZeroDivisionError?

Basta con especificar el tipo de excepción en la claúsula except. Veámoslo:

while True:
    try:
        dividendo = int(input('Introduce el dividendo: '))
        divisor = int(input('Introduce el divisor: '))
        print('El cociente de la división de ambos números es', dividendo//divisor)
    except ZeroDivisionError:
        print('Eso no ha estado bien, pero no pasa nada')
        print('Vuelve a intentarlo')

>>> 
Introduce el dividendo: 10
Introduce el divisor: 0
Eso no ha estado bien, pero no pasa nada
Vuelve a intentarlo
Introduce el dividendo: 
Traceback (most recent call last):
  File "C:/Users/javier/Desktop/borrable.py", line 3, in <module>
    dividendo = int(input('Introduce el dividendo: '))
KeyboardInterrupt

Como vemos, la excepción ZeroDivisionError ha sido capturada, pero no KeyboardInterrupt, ocasionada al teclear Ctrl-C con la intención de terminar la ejecución del programa.

Vayamos un paso más adelante. Escribamos ahora código que proteja no sólo de ZeroDivisionError, sino también de ValueError. Para ello, es suficiente con introducir ambas excepciones, separadas con una coma y entre paréntesis junto a la palabra except:

while True:
    try:
        dividendo = int(input('Introduce el dividendo: '))
        divisor = int(input('Introduce el divisor: '))
        print('El cociente de la división de ambos números es', dividendo//divisor)
    except (ZeroDivisionError, ValueError):
        print('Eso no ha estado bien, pero no pasa nada')
        print('Vuelve a intentarlo')

Comprobémoslo:

>>> 
Introduce el dividendo: 12
Introduce el divisor: 0
Eso no ha estado bien, pero no pasa nada
Vuelve a intentarlo
Introduce el dividendo: 12
Introduce el divisor: abcde
Eso no ha estado bien, pero no pasa nada
Vuelve a intentarlo
Introduce el dividendo: 12
Introduce el divisor: 4
El cociente de la división de ambos números es 3
Introduce el dividendo: 
Traceback (most recent call last):
  File "C:/Users/javier/Desktop/borrable.py", line 3, in <module>
    dividendo = int(input('Introduce el dividendo: '))
KeyboardInterrupt

Ctrl-C ha podido interrumpir el programa, pero no la división por cero ni el uso de letras en lugar de números.

El código siguiente ejecutará un tratamiento diferente según la excepción que se produzca. Observa el uso de varias claúsulas except:


while True:
    try:
        dividendo = int(input('Introduce el dividendo: '))
        divisor = int(input('Introduce el divisor: '))
        print('El cociente de la división de ambos números es', dividendo//divisor)
    except ZeroDivisionError:
        print('¡No, no, la división por cero no está permitida!')
    except ValueError:
        print('¡Ojo, presta atención e introduce sólo números!')

No sólo hemos capturado selectivamente las excepciones ZeroDivisionError y ValueError, sino que, además, el programa responderá de modo distinto según cuál suceda. Obsérvalo:

>>> 
Introduce el dividendo: 10
Introduce el divisor: 0
¡No, no, la división por cero no está permitida!
Introduce el dividendo: 10
Introduce el divisor: abcde
¡Ojo, presta atención e introduce sólo números!
Introduce el dividendo: 
Traceback (most recent call last):
  File "C:/Users/javier/Desktop/borrable.py", line 3, in <module>
    dividendo = int(input('Introduce el dividendo: '))
KeyboardInterrupt

Hemos salido del bucle infinito pulsando Ctrl-C, pues KeyboardInterrupt no ha sido capturada.

Naturalmente, puedes incluir tantos except personalizados como excepciones desees tratar.

Aún podemos rizar el rizo…

Imagina que, además de lo que hemos hecho en el ejemplo anterior, en el que hemos capturado y tratado de modo diferente dos excepciones, queremos programar una respuesta general para cualquier otra excepción. Agregamos, simplemente, una nueva claúsula except genérica que no incluya ningún nombre de excepción:

while True:
    try:
        dividendo = int(input('Introduce el dividendo: '))
        divisor = int(input('Introduce el divisor: '))
        print('El cociente de la división de ambos números es', dividendo//divisor)
    except ZeroDivisionError:
        print('¡No, no, la división por cero no está permitida')
    except ValueError:
        print('¡Ojo, presta atención e introduce sólo números!')
    except:
        print('No sé qué has hecho, pero vuelve a intentarlo')

Y ahora:

>>> 
Introduce el dividendo: 10
Introduce el divisor: 0
¡No, no, la división por cero no está permitida
Introduce el dividendo: 10
Introduce el divisor: abcde
¡Ojo, presta atención e introduce sólo números!
Introduce el dividendo: 10
Introduce el divisor: 
No sé qué has hecho, pero vuelve a intentarlo
Introduce el dividendo: 

En el tercer intento hemos pulsado Ctrl-C, por eso aparece el mensaje que hemos reservado para otras excepciones.

El tratamiento genérico debe figurar siempre en último lugar, después de los especializados. De lo contrario, el programa devolverá un error sintáctico al tratar de ejecutarlo. Y me temo que ese tipo de errores no hay try/except que los capture.

Esto se ha puesto interesante, pero aún hay mucho más que decir sobre el tratamiento de excepciones. Permanece atento…

Javier Montero Gabarró


Python – Captura selectiva de excepciones


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python, un lenguaje excepcional

Objetivo: Introducción al manejo de excepciones en Python.

Admítelo: los errores en tiempo de ejecución suceden. Tanto si intentas abrir un fichero inexistente como si tratas de utilizar un índice fuera del rango de una lista o string, por poner dos ejemplos comunes, la respuesta de Python siempre es desagradable. Si tienes suerte y estás trabajando desde el intérprete interactivo sólo recibirás el temido mensaje de error y podrás continuar. Pero si estás ejecutando un programa éste abortará inmediatamente.

Esto casi nunca es deseable. Imagina, por ejemplo, que tu aplicación facilita un servicio importante que requiere una disponibilidad de 24 horas. El daño ocasionado por tener tu programa parado podría ser importante (económico, reputación, imagen, etc.).

Puedes intentar aprender de los errores y modificar tu programa introduciendo líneas y líneas de código hasta hacerlo inexpugnable. Puede incluso que descubras que tienes más código escrito para tratar errores que código efectivo, lo que, en cierto modo sonaría un tanto ridículo.

Pero no hace falta que tu proyecto sea de envergadura para que te preocupes por gestionar adecuadamente los eventuales errores en tiempo de ejecución. Después de todo, si has elegido Python es porque eres una persona preocupada por la elegancia programando, de modo que tienes un compromiso contigo mismo.

Para lidiar con los errores en tiempo de ejecución Python dispone de mecanismos de gestión de excepciones.

Una excepción, en el mundo de la programación, no es más que una señal que lanza el programa advirtiendo que ha sucedido algo excepcional. Lo típico es un error en tiempo de ejecución pero, como descubriremos a lo largo de estos artículos, hasta tú mismo podrías lanzar tu propia señal de excepción de un modo creativo.

Ya conoces la respuesta de Python ante una excepción provocada por un error en tiempo de ejecución: pantallazo de error y el programa aborta. Pero, ¿y si pudiéramos modificar ese comportamiento por defecto a nuestro antojo, evitando así que el programa caiga?

Es lo que se conoce como capturar la excepción. Un ejemplo sencillo aclarará esto.

El error en tiempo de ejecución de libro típico es el intento de división entre cero, una cosa muy, muy mala, que puede hacer reventar tu sistema…

El siguiente programita pide, en un bucle infinito, que introduzcas dos números para dividirlos a continuación.

while True:
    dividendo = int(input('Introduce el dividendo: '))
    divisor = int(input('Introduce el divisor: '))
    print('El cociente de la división de ambos números es', dividendo//divisor)

Vamos a jugar un poco con él:

Introduce el dividendo: 23
Introduce el divisor: 5
El cociente de la división de ambos números es 4
Introduce el dividendo: 12
Introduce el divisor: 7
El cociente de la división de ambos números es 1
Introduce el dividendo: 

Todo bien, hasta ahora. Pero, ¿qué sucede si por divisor introducimos un cero?

Introduce el dividendo: 12
Introduce el divisor: 0
Traceback (most recent call last):
  File "C:/Users/Javier/Desktop/borrable.py", line 4, in <module>
    print('El cociente de la división de ambos números es', dividendo//divisor)
ZeroDivisionError: integer division or modulo by zero

Se acabó el programa abruptamente. Se ha producido un error en tiempo de ejecución ocasionado por intentar dividir entre cero, lo que ha ocasionado que se lanzara la excepción ZeroDivisionError, como nos muestra el texto del error.

Podríamos haber hecho daño al programa de más maneras. Observa cómo introducimos los valores con la función input(), que ya sabes que devuelve un string. Por eso recurrimos después a int() para convertir ese valor en un verdadero número entero con el que poder realizar operaciones aritméticas. Pero, ¿y si no podemos convertir lo introducido a entero porque no hemos facilitado un número?

Introduce el dividendo: casa
Traceback (most recent call last):
  File "C:/Users/Javier/Desktop/borrable.py", line 2, in <module>
    dividendo = int(input('Introduce el dividendo: '))
ValueError: invalid literal for int() with base 10: 'casa'

El programa aborta también, pero está vez devuelve un error diferente. Se ha producido una excepción del tipo ValueError.

Este comportamiento no es reprochable en Python, que desconoce el efecto que podría tener mantener el programa activo pese al error sucedido. Lo más prudente es informar de lo sucedido y abortar inmediatamente la ejecución, dejando al programador (tú), si lo desea, la captura de las excepciones.

Para capturar una excepción Python dispone de la estructura try/except. En la parte try incluimos el bloque de instrucciones que queremos proteger y en el except lo que queremos que se ejecute si sucede una excepción.

Vamos a proteger el código ante los dos eventuales errores que hemos descubierto que podrían sucedernos. Por un lado hay que proteger los input y por otro la línea con el resultado de la división, de modo que en bloque try incluimos las tres líneas críticas:

while True:
    try:
        dividendo = int(input('Introduce el dividendo: '))
        divisor = int(input('Introduce el divisor: '))
        print('El cociente de la división de ambos números es', dividendo//divisor)
    except:
        print('Eso no ha estado bien, pero no pasa nada')
        print('Vuelve a intentarlo')

Presta mucha atención a la indentación, pues delimita, como siempre, la amplitud de los bloques en Python y permite saber a que try corresponde cada except.

Observa cómo, en la sección except, incluiremos código especial que sólo se ejecutará si se produce una excepción dentro del bloque protegido con try.

Vamos a verlo en acción:

Introduce el dividendo: 14
Introduce el divisor: 5
El cociente de la división de ambos números es 2
Introduce el dividendo: 35
Introduce el divisor: 0
Eso no ha estado bien, pero no pasa nada
Vuelve a intentarlo
Introduce el dividendo: 46
Introduce el divisor: casa
Eso no ha estado bien, pero no pasa nada
Vuelve a intentarlo
Introduce el dividendo: 12
Introduce el divisor: 5
El cociente de la división de ambos números es 2
Introduce el dividendo: 

Ahora sí, a prueba de bombas. ¡Está protegido incluso ante un intento de interrupción con Ctrl-C! (excepción KeyboardInterrupt).

Esto ha sido sólo un aperitivo. En los sucesivos artículos sobre excepciones iremos refinando y ampliando estos conceptos, esperando los más sofisticados a que estemos en posesión previamente de ciertos conocimientos necesarios sobre las clases y la programación orientada a objetos. Todo a su momento.

Javier Montero Gabarró


Python, un lenguaje excepcional


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – Eliminación de múltiples elementos repetidos de una lista

Objetivo: presentar algunas técnicas en Python para la eliminación de elementos que aparecen repetidos en una lista.

La necesidad de eliminar elementos en una lista es algo común en cualquier proyecto de programación. Repasaremos, en primer lugar, la eliminación de un elemento simple empleando los recursos propios del objeto list para luego explicar cómo proceder en el caso de que queramos suprimir elementos repetidos.

Partamos del siguiente problema simple: dada una lista queremos eliminar cierto elemento dado. Por ejemplo, supongamos que nos ofrecen un gazpacho preparado del siguiente modo:

>>> gazpacho = ['aceite', 'vinagre', 'leche', 'tomate', 'pan', 'pepino', 'pimiento']

Sobre gustos no hay nada escrito, pero yo, desde luego, me cuidaría de probar semejante combinación. Dudo mucho que la leche haga buenas migas con los restantes ingredientes.

Aprovechemos nuestros conocimientos sobre Python para devolver esa pócima imbebible a su estado natural. Como ya sabemos por artículos anteriores, suprimir la leche es tan simple como esto:

>>> gazpacho.remove('leche')
>>> gazpacho
['aceite', 'vinagre', 'tomate', 'pan', 'pepino', 'pimiento']

Y ahora sí que me tomaría siete vasos seguidos.

El método remove modifica la lista in-situ, atacando al objeto original y suprimiento directamente en él el elemento indicado. Comprobamos que efectivamente la eliminación ha sido in-situ constatando que la invocación del método remove no ha devuelto ningún valor en el intérprete (si no sabes de qué estoy hablando, te aconsejo encarecidamente que te leas el artículo Hace falta valor, que te ayudará a comprender lo que está sucediendo entre bastidores).

El método remove tiene una limitación: sólo elimina la primera aparición del elemento en la lista. Pero, ¿y si queremos que desaparezca completamente si está repetido más veces?

Existen muchas formas de resolver esto; yo voy a mostrarte dos de las más elegantes.

La primera es casi obvia: descomponiendo el problema en otros más simples. La eliminación de muchos elementos no es más que la eliminación de uno solo unas cuantas veces.

Supongamos la siguiente colección de cine infantil:

cine = ['Bambi', 'Blancanieves', 'Emmanuelle', 'El rey león', 'Emmanuelle']

Parece que se nos ha colado alguna cinta ajena a la temática. El siguiente bucle las elimina de una en una aplicando el método remove:

>>> while 'Emmanuelle' in cine:
  cine.remove('Emmanuelle')

  
>>> cine
['Bambi', 'Blancanieves', 'El rey león']

Hemos hecho uso del operador in para comprobar la existencia del elemento en la lista.

Pero mi método favorito es recurriendo a la elegancia de las listas por comprensión.

Reconstruyamos nuestra colección original primero, modificada inapelablemente tras cada remove.

cine = ['Bambi', 'Blancanieves', 'Emmanuelle', 'El rey león', 'Emmanuelle']

Recordemos las distintas fases:

1) recorremos el iterable:

for peli in cine

2) escribimos a la izquierda la transformación deseada. No queremos transformar nada, sólo dejar cada película tal y como está:

peli for peli in cine

3) a la derecha escribimos la condición de filtrado:

peli for peli in cine if peli != 'Emmanuelle'

Estamos diciendo que sólo se seleccionen aquellos elementos distintos a ‘Emmanuelle’.

4) encerramos el conjunto entre llaves y ¡listo!:

>>> [peli for peli in cine if peli != 'Emmanuelle']
['Bambi', 'Blancanieves', 'El rey león']

Elegante. Acostúmbrate a usar las listas por comprensión, representan potencia sutil con la que podrás resolver numerosos problemas.

Aquí no es solo una cuestión de elegancia. Si te fijas, esta vez la invocación ha provocado que el intérprete devolviera un valor. Eso significa que ha sido creado un nuevo objeto.

En efecto, la lista original no ha sido afectada, como podemos comprobar:

>>> cine
['Bambi', 'Blancanieves', 'Emmanuelle', 'El rey león', 'Emmanuelle']

Lo que te permite recuperar Emmanuelle, todo un clásico del cine erótico digno de cualquier filmoteca que se precie.

Pero, si tu intención era esa, podrías haber procedido directamente del siguiente modo:

>>> cine = [peli for peli in cine if peli != 'Emmanuelle']
>>> cine
['Bambi', 'Blancanieves', 'El rey león']

Forzando así que la variable cine referencie ahora al nuevo objeto creado en lugar de al anterior, evitándote así oscuras tentaciones.

Javier Montero Gabarró


Python – Eliminación de múltiples elementos repetidos de una lista


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – Troceando y recomponiendo strings

Objetivo: presentar los métodos split() y join() para descomponer y recomponer strings, respectivamente.

Voy a enseñarte un sencillo truco de magia que puedes agregar a tu kit de herramientas en Python y que sin duda te será de gran utilidad en tu día a día como programador.

Para ello voy a necesitar tu colaboración. Confía en mí y déjame un billete, el de más valor que tengas. Ese mismo de color púrpura, por ejemplo, servirá.

>>> billete = 'Esto es un billete de 500 euros'

(No he visto un billete de 500 euros en mi vida. He tenido que recurrir a la wikipedia para consultar su color).

Ahora voy a romperlo delante de tus narices:

>>> trocitos_de_billete = billete.split()
>>> trocitos_de_billete
['Esto', 'es', 'un', 'billete', 'de', '500', 'euros']

O al menos lo era.

El método split(), aplicado a un string, crea una lista en la que cada elemento es una palabra del string. Como si de una butifarra se tratase, split() ha troceado la cadena de caracteres en sus palabras individuales y las ha guardado en una lista.

El modo de reconocer donde acaba y empieza una palabra es obvio: no tiene más que localizar cada espacio en blanco de separación y romper ahí.

Lo interesante de split() es que no le importa si hay más espacios en blancos de los debidos:

>>> dicho = 'Las palabras se las lleva el              viento'
>>> dicho.split()
['Las', 'palabras', 'se', 'las', 'lleva', 'el', 'viento']

Ni siquiera si esos espacios son tabuladores o saltos de línea:

>>> misterio = 'Dónde\testán\t\tlas llaves\nmatarile\nmatarile'
>>> print(misterio)
Dónde  están    las llaves
matarile
matarile
>>> 
>>> misterio.split()
['Dónde', 'están', 'las', 'llaves', 'matarile', 'matarile']

Para él es como si fueran un único espacio.

El método split() nos permite ciertas opciones de juego. Podemos, por ejemplo, indicar que el separador sea otro en lugar del espacio en blanco:

>>> gazpacho = 'tomate-pepino-pimiento-aceite-vinagre'
>>> gazpacho.split('-')
['tomate', 'pepino', 'pimiento', 'aceite', 'vinagre']

Algo que no tiene por qué limitarse a un único carácter:

>>> extensiones = '201abc202abc203'
>>> extensiones.split('abc')
['201', '202', '203']

También podemos precisar el número de veces que meteremos el cuchillo:

>>> python = '1 Python Programas Generador de claves aleatorias'
>>> python.split(' ', 3)
['1', 'Python', 'Programas', 'Generador de claves aleatorias']

Observa que, al necesitar el segundo parámetro, me he visto obligado a indicar el primero, el separador.

Pero podría haberlo obviado del siguiente modo:

>>> python.split(maxsplit = 3)
['1', 'Python', 'Programas', 'Generador de claves aleatorias']

Planteemos ahora el problema inverso: dada una lista de strings, fusionarla en una única cadena empleando determinado carácter como separador.

Los objetos de tipo string disponen de un método que logra precisamente eso: join().

Percátate de una sutileza en la frase anterior: he dicho que el método join() es propio del tipo string y no del tipo list. Por lo tanto no se puede aplicar sobre una lista sino sobre un string.

Entonces, si disponemos de dos elementos, la lista que queremos concatenar y el separador, ¿sobre cuál de ellos aplicaremos el join()? Sobre el separador, no hay otra opción, facilitando como argumento la lista.

>>> estaciones = ['Primavera', 'Verano', 'Otoño', 'Invierno']
>>> ' '.join(estaciones)
'Primavera Verano Otoño Invierno'

El separador puede ser cualquier string, obviamente:

>>> ' <---> '.join(estaciones)
'Primavera <---> Verano <---> Otoño <---> Invierno'

Volvamos entonces a nuestro truco de magia, con el billete de quinientos euros destrozado:

>>> trocitos_de_billete
['Esto', 'es', 'un', 'billete', 'de', '500', 'euros']

Reconstruirlo es tan simple como esto:

>>> billete_reconstruido = ' '.join(trocitos_de_billete)
>>> billete_reconstruido
'Esto es un billete de 500 euros'

Naturalmente, todo ha sido un truco. Aunque me vieras romper el billete delante de tus narices, eso nunca sucedió. El billete original, como en todos los buenos trucos, nunca sufrió daño alguno:

>>> billete
'Esto es un billete de 500 euros'

Javier Montero Gabarró


Python – Troceando y recomponiendo strings


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – Siete novias para siete hermanos

Objetivo: presentar la función zip() para emparejar elementos en Python.

En la película Siete novias para siete hermanos los hermanos Pontipee se ven envueltos en la azarosa empresa de tener que encontrar pareja. Aunque lo consiguen y sea aplicable el dicho de que está bien lo que bien acaba, hay que admitir que los medios empleados son poco ortodoxos y un tanto cuestionables.

Python proporciona maneras mucho más elegantes de hacer lo mismo…

Sea hermanos una lista con los ansiosos Pontipee:

>>> hermanos = ['Adam', 'Benjamin', 'Caleb', 'Daniel', 
'Efraín', 'Frank', 'Gideon']

Y sea novias la lista de pretendidas:

>>> novias = ['Milly', 'Dorcas', 'Ruth', 'Martha', 
'Liza', 'Sarah', 'Alice']

Para asociar un elemento de una lista con su respectivo de otra, Python dispone de la función zip(), palabra en inglés que significa cremallera.

Visualiza por un instante una cremallera desabrochada. A ambos lados del deslizador dos filas de dientes esperan ser engarzados. Eso es precisamente lo que hace zip().

>>> zip(hermanos, novias)
<zip object at 0x02C53670>

La función zip() ha creado un objeto de tipo zip que no es otra cosa sino un iterador. Hablaremos con más profundidad sobre iteradores e iterables en otro momento, pero por ahora puedes quedarte con la idea de que pueden ser iterados, es decir, recorridos de principio a fin (con un bucle for, por ejemplo).

El objeto iterador tal cual, crudo, no puede ser visualizado directamente; para hacerlo
debemos otorgarle cierta estructura ósea. Es como si se tratara de un fantasma translúcido, incorpóreo, pero visible si volcamos un paquete de harina sobre él.

Materialicemos nuestro iterador convirtiéndolo, por ejemplo, en lista:

>>> list(zip(hermanos, novias))
[('Adam', 'Milly'), ('Benjamin', 'Dorcas'), ('Caleb', 'Ruth'), 
('Daniel', 'Martha'), ('Efraín', 'Liza'), ('Frank', 'Sarah'), 
('Gideon', 'Alice')]

Y ahora sí, cada oveja con su pareja.

La función zip(), hablando con más propiedad, toma iterables como argumentos (pueden ser más de dos, visualiza una cremallera tridimensional) y devuelve un iterador compuesto por tuplas, como puedes comprobar en el ejemplo anterior.

Los iterables presentes en los argumentos no tienen que ser necesariamente de la misma longitud. En ese caso, empareja los que pueda, el resto los omite:

>>> a = [0, 1, 2, 3]
>>> b = [1, 10, 100, 1000, 10000, 100000]
>>> for (exponente, resultado) in zip(a,b):
  print('10 elevado a', exponente, '=', resultado)

  
10 elevado a 0 = 1
10 elevado a 1 = 10
10 elevado a 2 = 100
10 elevado a 3 = 1000

Presta atención a la técnica empleada para desempaquetar los elementos individuales de las tuplas generadas por zip().

THE END

Javier Montero Gabarró


Python – Siete novias para siete hermanos


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python: El producto cartesiano

Objetivo: mostrar una técnica eficiente para determinar el producto cartesiano de dos conjuntos empleando listas por comprensión.

Imagina que entre la equipación deportiva que incluye tu armario figuran camisetas de cuatro colores: blanco, verde, azul y amarillo. Dispones, además, de tres tipos de pantalones: largo, medio y corto.

¿De cuántas maneras distintas podrías vestirte para salir a correr?

La respuesta la facilita el concepto de producto cartesiano, que con certeza estudiaste en los primeros cursos de matemáticas. Un breve repaso…

Sean dos conjuntos independientes A y B, al conjunto de todas las agrupaciones posibles que se forman tomando un elemento de A y otro de B, se le denomina producto cartesiano de A y B y se denota como A x B.

Además, el número de elementos de que consta A x B es el número de elementos de A multiplicado por el número de elementos de B.

Para referirnos al número de elementos de un conjunto recurrimos al término cardinal, de modo que:

card(A x B) = card(A) x card(B)

Este concepto es extensible a más de dos conjuntos: el cardinal del producto cartesiano de varios conjuntos es el producto de los cardinales individuales.

En nuestro ejemplo, si tenemos cuatro tipos de camisetas y tres de pantalones, dispondremos de 12 maneras diferentes de vestirnos.

Nuestra labor pythonesca va a ser listar todos estos pares y para ello recurriremos a la técnica de las listas por comprensión que presentamos en los últimos artículos y que ahora refinaremos de un modo particular.

Comencemos definiendo las estructuras:

>>> camisetas = {'blanca', 'verde', 'azul', 'amarilla'}
>>> pantalones = {'largo', 'medio', 'corto'} 

He elegido conjuntos por mayor similitud conceptual, pero, naturalmente, puedes emplear cualquier tipo de secuencia como las listas o las tuplas.

Creemos nuestra lista por comprensión:

1) Recorremos el iterable:

for c in camisetas for p in pantalones

Esta es el punto crítico al que quería llegar. En las listas por comprensión podemos anidar tantos for como deseemos.

2) A la izquierda de lo anterior escribimos lo que deseamos obtener:

(c, p) for c in camisetas for p in pantalones

He optado por agrupar cada par en una tupla.

3) A la derecha del todo escribimos la condición de filtrado.

En este ejemplo no hay ninguna, por lo que no es aplicable.

4) Rodeamos toda la expresión entre llaves y ya tenemos creada la lista por comprensión.

>>> [(c, p) for c in camisetas for p in pantalones]
[('verde', 'corto'), ('verde', 'medio'), ('verde', 'largo'), 
('amarilla', 'corto'), ('amarilla', 'medio'), ('amarilla', 'largo'), 
('azul', 'corto'), ('azul', 'medio'), ('azul', 'largo'), 
('blanca', 'corto'), ('blanca', 'medio'), ('blanca', 'largo')]

Programar con eficacia y eficiencia supone armarse con una buena colección de usos típicos para resolver problemas frecuentes, como por ejemplo hallar el producto cartesiano. Las listas por comprensión, sin duda, figurarán en muchos de esos usos.

Javier Montero Gabarró


Python: El producto cartesiano


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – Listas por comprensión – 2

Objetivo: presentar la sintaxis extendida de la generación de listas por comprensión en Python.

En el artículo anterior mostramos la elegancia de Python en la forma de las listas por comprensión, mecanismo que nos permitía, con una única instrucción, generar una lista a través de la transformación de los elementos de un iterable.

Vamos a dar un pase adelante y refinemos la creación de esa lista mediante la introducción de un filtro selectivo.

Dado el siguiente listado de alumnos de una clase:

>>> alumnos = ['Ana', 'Luis', 'Pedro', 'Marta', 'Nerea', 'Pablo']

Supongamos que queremos obtener las iniciales de todas las chicas del grupo.

Obtener simplemente las iniciales no debería plantearte mucho problema con las técnicas que ya conoces:

>>> [alumno[0] for alumno in alumnos]
['A', 'L', 'P', 'M', 'N', 'P']

Recorremos la lista alumnos de alumno en alumno y nos quedamos con la primera letra de cada uno (alumno[0]).

Pero, ¿podemos realizar el filtrado de sexo manteniendo la elegancia de las listas por comprensión?

Es preciso recurrir a la sintaxis extendida:

– Comenzamos recorriendo el iterable.

– Escribimos a la izquierda la transformación deseada.

– Escribimos a la derecha la condición de filtrado.

– Encerramos todo el conjunto entre corchetes.

He aquí la solución buscada:

>>> [alumno[0] for alumno in alumnos if alumno[-1] == 'a']
['A', 'M', 'N']

Observa que alumno[-1] representa a la última letra de cada nombre. Naturalmente, nos hemos permitido simplificar el problema presuponiendo que los nombres de mujer son aquellos que terminan en a.

Recuerda que las listas por comprensión no modifican la lista original sino que crean otra nueva.

El primer término no tiene que implicar necesariamente una transformación. Por ejemplo, si en vez de la inicial queremos el nombre completo, podríamos haber procedido así, obviamente:

>>> [alumno for alumno in alumnos if alumno[-1] == 'a']
['Ana', 'Marta', 'Nerea']

Vamos a complicarlo algo más. El siguiente diccionario contiene las calificaciones de esos alumnos:

>>> notas = {'Ana':9, 'Luis':7, 'Pedro':2, 'Marta':5, 'Nerea':4, 'Pablo':6}

Debemos obtener los nombres de todos aquellos que hayan aprobado:

Desglosemos la lista por comprensión en sus tres términos. Comenzamos por el central, correspondiente a la iteración:

for nombre, nota in notas.items()

Fíjate en el uso del método items() para poder iterar simultáneamente sobre las claves y los valores del diccionario.

En el primer término indicamos lo que queremos extraer:

nombre

Y en el tercero realizamos el filtrado:

if nota >= 5

Todo junto, entre corchetes, nos devuelve la lista buscada:

>>> [nombre for nombre, nota in notas.items() if nota >= 5]
['Ana', 'Luis', 'Pablo', 'Marta']

No te costará mucho habituarte a las listas por comprensión. Código limpio y eficiente, ¡Python es esto, quién podría resistirse?

Javier Montero Gabarró


Python – Listas por comprensión – 2


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Trigonometría en Python

Objetivo: presentar las funciones trigonométricas disponibles en el módulo math de Python.

La mejor manera de acercarse e ir conociendo la inmensa biblioteca estándar de Python es dividiéndola en pequeños bloques temáticos. En el artículo de hoy nos ocuparemos de un subconjunto muy específico: las funciones trigonométricas.

La primera aproximación a las matemáticas en Python es a través del módulo math, incluido directamente en la propia distribución. Más adelante hablaremos de extensiones de terceros como NumPy o SciPy que harán la delicia de matemáticos, físicos e ingenieros. No en vano, Python es cada vez más utilizado por la comunidad científica, que encuentra unidas toda la potencia del lenguaje junto a funcionalidad y herramientas que tradicionalmente sólo han estado disponibles adquiriendo costosos programas como Mathematica o Matlab.

Para disfrutar de todo lo que math nos ofrece debemos importarlo:

>>> import math

En el próximo artículo veremos otros estilos de importación, cada uno con sus ventajas e inconvenientes. Pero con esa simple instrucción tienes acceso ya a todo el espacio de nombres del módulo math, repleto de decenas de funciones entre las que se encuentran las que hoy trataremos.

Básicamente, necesitaremos poder calcular el seno, coseno y la tangente de un ángulo, así como sus funciones inversas (dada alguna de estas magnitudes, conocer a qué angulo se corresponden). Además, nos gustaría poder trabajar tanto en radianes como en grados, de modo que nos sería útil también poder disponer de las funciones de conversión adecuadas.

El seno, coseno y tangente de un ángulo los obtenemos con las funciones sin(), cos() y tan(), respectivamente:

>>> sin(0)
0.0
>>> cos(0)
1.0

Debes tener en cuenta que estas funciones esperan que el argumento se facilite en radianes, y no en grados. A tal efecto, te gustará saber que dispones de la siguiente constante:

>>> math.pi
3.141592653589793

de modo que podemos usar esa expresión en lugar del valor númérico:

>>> math.sin(math.pi/3)
0.8660254037844386
>>> math.cos(math.pi/3)
0.5000000000000001

No existe una función directa para la cosecante, secante y cotangente, pues no es necesario, ya que simplemente se trata de los recíprocos del seno, coseno y tangente, respectivamente.

Las funciones inversas, arcoseno, arcocoseno y arcotangente, se obtienen, respectivamente, con asin(), acos() y atan():

>>> math.atan(1)
0.7853981633974483

Del mismo modo que antes, el ángulo devuelto es en radianes también. Si queremos trabajar en grados deberemos efectuar la conversión con degrees(), que toma como argumento un ángulo en radianes y lo devuelve en grados:

>>> math.degrees(0.7853981633974483)
45.0

La operación contraria, el paso de grados a radianes, se obtiene con radians():

>>> math.radians(90)
1.5707963267948966

que es justamente \pi/2 radianes:

>>> math.pi/2
1.5707963267948966

Otra función útil de math nos permite conocer el valor de la hipotenusa de un triángulo rectángulo dados sus catetos, ahorrándonos tener que calcular la raíz cuadrada de la suma de los cuadrados de estos:

>>> math.hypot(3, 4)
5.0

Para finalizar, vamos a diseñar nuestra propia función seno, que denominaremos sen() (a nuestra usanza) y aceptará directamente ángulos en grados.

import math

def sen(grados):
    return math.sin(math.radians(grados))

Ejecútalo y ya podrás hacer cosas como esta:

>>> sen(30)
0.49999999999999994

En definitiva, ½, tal como nos enseñaron en la escuela…

Javier Montero Gabarró


Trigonometría en Python


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies