Programar Juegos Arcade
con Python y PygameChapter 18: Excepciones
¿Que te parecería si, por ejemplo, tu programa no funcionara bien, evitar que el usuario viese los típicos mensajes rojos de error de Python, o que el programa no se colgara? Si es así, lo que necesitas son excepciones.
Las excepciones las usamos para gestionar condiciones anormales que puedan aparecer durante la ejecución del código. Habitualmente se usan para operaciones de red o sobre archivos. Esto permite que el código salga airoso si ocurre, por ejemplo, un desbordamiento del disco, un error de red o problemas con determinados permisos.
18.1 Vocabulario
Los términos y frases más habituales cuando trabajamos con excepciones son:
- Excepción: Este término puede significar dos cosas. Primero, la condición que resulta de un flujo anormal del programa. O se puede usar para referirse al objeto que representa esa condición. Cada excepción posee un objeto que guarda la información sobre ella.
- Gestión de la excepción: El proceso de manejo de una excepción para conseguir un flujo normal del programa.
- Bloque de captura o bloque de excepción: Es el código que maneja una condición anormal, y se dice que “captura” la excepción.
- Lanzar (Throw) o levantar (raise): Cuando se ha detectado alguna condición anormal en el flujo del programa, se crea una instancia para el objeto excepcional, entonces es “levantado” hacia un código que lo capturará.
- Excepciones no gestionadas o Excepciones no capturadas: Una excepción que ha sido lanzada pero nunca capturada. Habitualmente termina en error y el programa se cuelga o cierra inesperadamente.
- Bloque try: Contiene el código que al ejecutarse podría generar una excepción.
La mayoría de lenguajes usan términos como “lanzar” y “capturar.” Desafortunadamente no es el caso de Python. Python emplea “levantar” y “excepción.” Nosotros hemos introducido los términos lanzar/capturar porque son los más utilizados en la industria.
18.2 Gestión de Excepciones
El código para manejar las excepciones es sencillo. Observa el siguiente ejemplo:
# Dividir entre cero try: x = 5 / 0 except: print("Error división entre cero")
Sobre la línea 2 tenemos la sentencia try. Todas las líneas indentadas siguientes pertenecen al “bloque try.” Debajo del bloque try no debería existir ningún código no-indentado que no empezara con la sentencia except. La sentencia try define una sección que el código intentará ejecutar.
Si apareciera cualquier excepción durante el procesamiento del código, la ejecución saltaría inmediatamente hacia el “bloque de captura” (“catch block.”) Esta parte del bloque se halla indentada bajo la sentencia except de la línea 4. Éste es el código responsable de manejar el error.
Podemos tener un programa que use excepciones para capturar errores que puedan aparecer durante la conversión de un texto a número, como por ejemplo:
# Conversión fallida a número try: x = int("fred") except: print ("Error al intentar convertir fred a número")
En la línea 3 se lanzará una excepción debido a que “fred” no puede ser convertido a número entero. El código de la línea 5 imprimirá el mensaje de error.
Debajo podemos ver una versión ampliada del ejemplo anterior. En este caso se comprueba si existe algún error en la entrada de usuario, asegurándose de que se ha introducido un número entero. Si el usuario no lo introduce, el programa continuará demandándole que lo haga. El código utiliza una excepción para capturar un posible error de conversión que pudiera ocurrir en la línea 5. Si se introdujera cualquier otra cosa que no fuera un entero, se lanzaría una excepción al intentarse la conversión en la línea 5. Si así fuera, el código de la línea 6, que establece numero_introducido a True, no se ejecutaría.
numero_introducido = False while not numero_introducido: numero_cadena = input("Introduce un número entero: ") try: n = int(numero_cadena) numero_introducido = True except: print ("Error, número entero incorrecto")
Los archivos son particularmente proclives a errores durante las operaciones con ellos; el disco podría llenarse por completo, un usuario podría borrar un archivo mientras se escribe sobre él, o la unidad USB ser extraída en mitad de una operación de lectura/escritura. Este tipo de errores pueden ser fácilmente capturados usando excepciones.
# Error al abrir un archivo try: mi_archivo = open("miarchivo.txt") except: print("Error al abrir el archivo")
Podemos capturar y procesar, de maneras distintas, muchos tipos de errores. Por ejemplo, sería muy útil mostrarle al usuario un mensaje de error más preciso que un simple; “ocurrió un error.”
En el siguiente ejemplo, entra las líneas 5-8, podemos encontrarnos con diferentes tipos de errores. Si colocamos un IOError luego de un except en la línea 9, solo los errores referidos a Entradas y Salidas (Input/Output) serán manejados por esta parte del código. Igualmente, la línea 11 solo maneja errores referidos a conversión de valores, y la línea 13, errores producidos por la división entre cero. La última excepción ocurre en la línea 15. Como en esta línea no se incluye ningún tipo particular de error, en ella se gestionarán aquellos errores no cubiertos por los anteriores bloques except. La sentencia except “captura-todo” debe ser siempre la última.
En la línea 1 se importa la biblioteca sys, que será utilizada en la línea 16 para imprimir el tipo de error ocurrido.
import sys # Errores múltiples try: mi_archivo = open("miarchivo.txt") mi_linea = mi_archivo.readline() mi_entero = int(s.strip()) mi_valor_calculado = 101 / mi_entero except IOError: print("I/O error") except ValueError: print("No he podido convertir el dato a número entero.") except ZeroDivisionError: print("División entre cero, error") except: print("Error Inesperado:", sys.exc_info()[0])
Una lista de las excepciones implementadas en Python se encuentra disponible en la siguiente dirección web:
http://docs.python.org/library/exceptions.html
18.3 Ejemplo: Almacenar La Máxima Puntuación
Aquí mostraremos cómo guardar la máxima puntuación (high score) entre juegos. Esta puntuación será almacenada en un archivo llamado high_score.txt.
""" # Sample Python/Pygame Programs # Simpson College Computer Science # http://programarcadegames.com/ # http://simpson.edu/computer-science/ """ def obtener_puntuacion_mas_alta(): # Puntuación más alta por defecto puntuacion_mas_alta = 0 # Intentemos leer la puntuación más alta desde un archivo try: archivo_puntuacion_mas_alta = open("high_score.txt", "r") puntuacion_mas_alta = int(archivo_puntuacion_mas_alta.read()) archivo_puntuacion_mas_alta.close() print("La puntuación más alta es", puntuacion_mas_alta) except IOError: # Error al leer el archivo, no existe una puntuación más alta print("Aún no existe una puntuación más alta.") except ValueError: # Hay un archivo allí, pero no entiendo los números. print("Estoy confundido. Empezamos sin una puntuación alta.") return puntuacion_mas_alta def guardar_puntuacion_mas_alta(nueva_puntuacion_mas_alta): try: # Escribimos el archivo en disco archivo_puntuacion_mas_alta = open("high_score.txt", "w") archivo_puntuacion_mas_alta.write(str(nueva_puntuacion_mas_alta)) archivo_puntuacion_mas_alta.close() except IOError: # Um, no puedo escribirlo. print("No soy capaz de guardar la puntuación alta.") def main(): """ Aquí va el programa principal. """ # Obtenemos la puntuación más alta. puntuacion_mas_alta = obtener_puntuacion_mas_alta() # Obtenemos la puntuación del juego en curso puntuacion_actual = 0 try: # Pregúntale al usuario/a por su puntuación puntuacion_actual = int(input ("¿Cuál es tu puntuación? ")) except ValueError: # Error, no puedo convertir lo que ha escrito en un número print("No comprendo qué has escrito.") # Observa por si tenemos una nueva puntuación más alta if puntuacion_actual > puntuacion_mas_alta: # Conseguido! Guardamos en disco print("Bravo! Nueva puntuación más alta!") guardar_puntuacion_mas_alta(puntuacion_actual) else: print("Mejor suerte la próxima vez.") # Llamamos a la función principal, empezamos el juego if __name__ == "__main__": main()
18.4 Objetos del tipo Excepción
A través de un objeto excepción, podemos extraer más información acerca de un error. Este objeto puede ser recogido mientras capturamos un error, usando la palabra clave as. Por ejemplo:
try: x = 5 / 0 except ZeroDivisionError as e: print(e)
La variable e apunta hacia una información imprimible más amplia sobre la excepción en cuestión. Podemos hacer más cosas con los objetos excepción, pero lamentablemente, eso está más allá del ámbito de este capítulo.
18.5 Generamos Una Excepción
Las excepciones pueden generarse con el comando raise. Por ejemplo:
# Generamos excepciones def get_input(): entrada_usuario = input("Escribe algo: ") if len(entrada_usuario) == 0: raise IOError("No has escrito nada") get_input()
Usando el código anterior, intenta añadir una excepción que maneje el IOError levantado.
También es posible crearnos excepciones personalizadas, pero esto también está fuera del ámbito de este capítulo. Los que tengan más curiosidad, pueden acudir a:
http://docs.python.org/tutorial/errors.html#raising-exceptions
18.6 Uso Apropiado de las Excepciones
Las excepciones no deberían ser utilizadas cuando una simple declaración if podría manejar la situación. El código normal no debería lanzar excepciones durante lo que se conoce como “happy path” (”prueba feliz”?). Un código try/catch bien implementado es fácil de leer, pero un código que incluye muchas excepciones y saltos puede ser una pesadilla a la hora de depurar. (Una vez tuve la tarea de depurar un código que leía un documento XML. El código generaba docenas de excepciones para cada línea que leía del archivo. Todo era enormemente lento y sujeto a errores. Ese código no debería haber generado una sola excepción en el curso normal de leer un archivo.)
18.7 Repaso
18.7.1 Test
18.7.2 Ejercicios
Haz click para ir a los Ejercicios.
You are not logged in. Log in here and track your progress.
English version by Paul Vincent Craven
Spanish version by Antonio Rodríguez Verdugo
Russian version by Vladimir Slav
Turkish version by Güray Yildirim
Portuguese version by Armando Marques Sobrinho and Tati Carvalho
Dutch version by Frank Waegeman
Hungarian version by Nagy Attila
Finnish version by Jouko Järvenpää
French version by Franco Rossi
Korean version by Kim Zeung-Il
Chinese version by Kai Lin