Python Avanzado II
Python Avanzado II
Python
Codo a Codo 4.0
Clase 26
Python – Parte 8
Errores y excepciones
En algunas ocasiones nuestros programas pueden fallar ocasionando su detención. Ya sea por
errores de sintaxis o de lógica, tenemos que ser capaces de detectar esos momentos y tratarlos
debidamente para prevenirlos.
Errores
Los errores detienen la ejecución del programa y tienen varias causas. Para poder estudiarlos
mejor vamos a provocar algunos intencionadamente y ver lo que nos muestra la terminal.
Errores de sintaxis
Identificados con el código SyntaxError, son los que podemos apreciar repasando el código, por
ejemplo al dejarnos de cerrar un paréntesis:
print("Hola" terminal
errores.py
Cuando leemos un valor con la función input(), este siempre se obtendrá como una cadena
de caracteres. Si intentamos operarlo directamente con otros números tendremos un fallo
TypeError que tampoco detectan los editores de código:
n = input("Ingrese un número: ")
m = 4
print("{}/{} = {}".format(n,m,n/m))
terminal
Errores semánticos (continuación)
Como ya sabemos este error se puede prevenir transformando la cadena a entero o flotante:
terminal
Tipos de excepciones
Exception FloatingPointError TypeError
ArithmeticError IOError UnboundLocalError
AssertionError ImportError UnicodeDecodeError
AttributeError IndentationError UnicodeEncodeError
BaseException IndexError UnicodeError
BufferError InterruptedError UnicodeTranslateError
ChildProcessError KeyError UnicodeWarning
ConnectionAbortedError KeyboardInterrupt UserWarning
ConnectionError ModuleNotFoundError ValueError
ConnectionRefusedError NameError Warning
ConnectionResetError NotImplementedError ZeroDivisionError
DeprecationWarning PermissionError
EOFError RecursionError
EnvironmentError ReferenceError
FileExistsError RuntimeError
FileNotFoundError StopIteration
Excepciones: Bloques try - except
Para prevenir el fallo debemos poner el código propenso a errores en un bloque try y luego
encadenar un bloque except para tratar la situación excepcional mostrando que ha ocurrido un
fallo:
try:
n = float(input("Ingrese un número: "))
m = 4
print("{}/{} = {}".format(n,m,n/m))
except:
print("Ha ocurrido un error, introduzca un número")
Ingrese un número: hola terminal Como vemos esta forma nos permite controlar
Ha ocurrido un error, introduzca un número situaciones excepcionales que generalmente
darían error y en su lugar mostrar un mensaje o
ejecutar una pieza de código alternativo.
excepciones-try-except.py
Podemos aprovechar las excepciones para forzar al usuario a introducir un número haciendo uso
de un bucle while, repitiendo la lectura por teclado hasta que lo haga bien y entonces romper el
bucle con un break:
while(True):
try:
n = float(input("Ingrese un número: "))
m = 4
print("{}/{} = {}".format(n,m,n/m))
break # Importante romper la iteración si todo ha salido bien
except:
print("Ha ocurrido un error, introduzca un número")
print('2' + 2) TypeError: can only concatenate str (not "int") to str terminal
excepciones-2.py
Propagación de excepciones
Durante la ejecución de un programa, si dentro de una función surge una excepción y la función
no la maneja, la excepción se propaga hacia la función que la invocó, si esta otra tampoco la
maneja, la excepción continua propagándose hasta llegar a la función inicial del programa y si
esta tampoco la maneja se interrumpe la ejecución del programa.
Marco global
Si introducimos un 0 como
divisor nos devolverá un
error de división por 0 que
se propaga a las demás main()
funciones.
funcion()
excepciones-3-propagacion.py
Propagación de excepciones
La forma de resolver el problema es incorporando un bloque try…except de modo tal que si se
introduce un 0 como divisor aparecerá “Algo salió mal”:
Marco global
main()
x: 2
normalizar()
terminal
y: 0
antes
Algo salió mal
excepciones-4.py
listo
Excepciones múltiples
En una misma pieza de código pueden ocurrir muchos errores distintos y quizá nos interese
actuar de forma diferente en cada caso.
Para esas situaciones algo que podemos hacer es asignar una excepción a una variable. De esta
forma es posible analizar el tipo de error que sucede gracias a su identificador:
try:
n = input("Ingrese un número: ") # No transformamos a número
5/n
except Exception as e: # Guardamos la excepción como una variable e
print("Ha ocurrido un error =>", type(e).__name__)
Cada error tiene un identificador único que curiosamente se corresponde con su tipo de dato.
Aprovechándonos de eso podemos mostrar la clase del error, dentro de except, utilizando la
sintaxis:
print(type(e)) <class 'TypeError'> terminal
Es similar a conseguir el tipo (o clase) de cualquier otra variable o valor literal:
Como vemos siempre nos indica "class" delante. Eso es porque en Python todo son clases. Lo
importante ahora es que podemos mostrar solo el nombre del tipo de dato (la clase) consultando
su propiedad especial name de la siguiente forma:
excepciones-multiples.py
Invocación de excepciones
En algunas ocasiones quizá nos interesa llamar a un error manualmente, ya que un print común
no es muy elegante:
def mi_funcion(algo=None):
if algo is None:
print("Error! No se permite un valor nulo (con un print)")
mi_funcion()
Instrucción raise
Gracias a raise podemos lanzar una excepción pasándole el identificador. Luego simplemente
podemos añadir un except para tratar esta excepción que hemos lanzado:
def mi_funcion(algo=None):
try:
if algo is None:
raise ValueError("Error! No se permite un valor nulo")
except ValueError:
print("Error! No se permite un valor nulo (desde la excepción)")
mi_funcion()
Error! No se permite un valor nulo (desde la excepción) terminal
Instrucción raise (continuación)
• Se utiliza para crear una excepción.
• Si no se detalla el tipo de excepción, se relanza la última excepción producida.
• Como desarrollador, se puede elegir lanzar una excepción si se produce una condición.
• Para “lanzar” una excepción, debemos usar la palabra clave raise.
• Puede definir qué tipo de error generar y el texto para imprimir al usuario.
• Ejemplo: lanzar una excepción del tipo TypeError si x no es un entero:
raise.py
Fuente: https://www.w3schools.com/python/gloss_python_raise.asp
Instrucción assert
• La declaración assert es una forma de generar una excepción si no ocurre la afirmación
esperada.
• La excepción AssertionError se genera cuando una declaración assert no se cumple.
• Ejemplo: crear una función para validar el ingreso de números naturales.
def ingresarNatural(msj):
while True:
try:
valorReal= float(input(msj))
valor= int(valorReal)
assert (valor == valorReal), "Error: debe ingresar un valor entero."
assert (valor > 0), "Error: debe ingresar un valor positivo."
break
except AssertionError as error: sigue…
print(error)
except (ValueError):
print("Error: ha ingresado un valor que no es numérico.")
except:
print("Error inesperado.") # Programa principal
return valor def __main__():
msj= "Ingrese un número natural: "
num= ingresarNatural(msj)
print(num)
assert.py
if __name__ == '__main__':
__main__()
Instrucción sys.exc_info()
• La última cláusula except puede omitir el nombre de la excepción, a modo de “comodín”.
• Usar esto con extremo cuidado, ya que es muy fácil enmascarar un error de programación de
esta manera.
• También se puede usar para imprimir un mensaje de error emitido por el sistema y luego
volver a generar (re-lanzar) la excepción (permitiendo que quien llama también maneje la
excepción).
import sys
try:
arch= open('miArch.txt')
txt = arch.readline()
num = int(txt.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Error: no se pudo convertir el valor a entero.")
except:
print("Error inesperado:", sys.exc_info()[0]) # Me informa el tipo de excepción (class)
raise # Re-lanza la excepción
sys.exc_info().py
Ejercicios resueltos
Localizar el error en los siguientes bloques de código. Crear una excepción para evitar que el
programa se bloquee y además explicar en un mensaje al usuario la causa y/o solución:
Ejercicio 1
Ejercicio 2
lista = [1, 2, 3, 4, 5] Solución
lista = [1, 2, 3, 4, 5]
lista[10] try:
lista[10]
except IndexError:
print("El índice se encuentra fuera de rango")
resultado = 15 + "20"
try: Solución
resultado = 15 + "20"
except TypeError:
print("Sólo es posible sumar datos del mismo tipo")
Sólo es posible sumar datos del mismo tipo terminal
Excepciones - Resumen
try: try: try: try:
<instrucciones> <instrucciones> <instrucciones> <instrucciones>
except: except <tipo1>: except <tipo1> as <nombre>: except <tipo1> as <nombre>:
<instrucciones> <instrucciones> <instrucciones> <instrucciones>
except <tipo2>: except <tipo2> as <nombre>: except <tipo2> as <nombre>:
<instrucciones> <instrucciones> <instrucciones>
except: except: except:
<instrucciones> <instrucciones> <instrucciones>
finally:
<instrucciones>
• Podemos capturar excepciones para evitar que el programa finalice por un error. Bloque: try... except
• Se ejecuta el bloque try. Si no ocurre ninguna excepción, el bloque except se saltea.
• Si ocurre una excepción durante la ejecución del bloque try, el resto del bloque se saltea. Si su tipo
coincide con la excepción, se ejecuta el bloque except.
• Una declaración try puede tener más de un except.
• El último except puede omitir el tipo de dato (evitar su uso) se ejecuta ante cualquier error que ocurra en
el bloque y no esté gestionado anteriormente.
• else: es opcional en un bloque try except. Sólo será ejecutada cuando todas las instrucciones del bloque
try se ejecutaron en forma “normal”.
• finally: garantiza que un bloque de código siempre se ejecute, sin importar si hubo o no errores.
Excepciones – Más ejemplos
Crear una función para validar el ingreso de un número positivo:
def ingresarPositivo(msj):
err= True
while err:
try:
num= float(input(msj))
if num>=0:
err=False
else:
print("Error: debe ingresar un valor positivo o igual a 0.")
except (ValueError):
print("Error: ha ingresado un valor no numérico.")
except:
print("Error inesperado.")
else: Ingrese un número positivo: -1 terminal
if not err: Error: debe ingresar un valor positivo o
break igual a 0.
return num Ingrese un número positivo: hola
Error: ha ingresado un valor no numérico.
ingresarPositivo("Ingrese un número positivo: ") Ingrese un número positivo: 3
excepciones-ingresarPositivo.py
Módulos
Los módulos son ficheros que contienen definiciones que se pueden importar en otros scripts
para reutilizar sus funcionalidades.
Anteriormente vimos que crear un módulo en Python es tan sencillo como crear un script, sólo
tenemos que añadir alguna función a un fichero con la extensión .py, por ejemplo saludos.py:
def saludar():
print("Hola, te estoy saludando desde la función saludar()")
Luego ya podremos utilizarlo desde otro script, por ejemplo script.py, en el mismo directorio
haciendo un import y el nombre del módulo:
import saludos
saludos.saludar()
También podemos importar funciones directamente, de esta forma ahorraríamos memoria.
Podemos hacerlo utilizando la sintaxis from import:
saludar()
Dicho esto, aparte de funciones también vimos que podemos reutilizar clases:
class Saludo():
def __init__(self):
print("Hola, te estoy saludando desde el __init__")
Igual que antes, tendremos que llamar primero al módulo para referirnos a la clase:
s = Saludo()
El problema ocurre cuando queremos utilizar nuestro módulo desde un directorio distinto por
ejemplo test/script.py.
ModuleNotFoundError: No module named 'saludos' terminal
Paquetes
Utilizar paquetes nos ofrece varias ventajas. En primer lugar nos permite unificar distintos
módulos bajo un mismo nombre de paquete, pudiendo crear jerarquías de módulos y
submódulos, o también subpaquetes.
Por otra parte nos permiten distribuir y manejar fácilmente nuestro código como si fueran
librerías instalables de Python. De esta forma se pueden utilizar como módulos estándar desde el
intérprete o scripts sin cargarlos previamente.
Para crear un paquete lo que tenemos que hacer es crear un fichero especial init vacío en el
directorio donde tengamos todos los módulos que queremos agrupar. De esta forma cuando
Python recorra este directorio será capaz de interpretar una jerarquía de módulos:
script.py
paquete/
__init__.py
saludos.py
Ahora, si utilizamos un script desde el mismo directorio donde se encuentra el paquete podemos
acceder a los módulos, pero esta vez refiriéndonos al paquete y al módulo, así que debemos
hacerlo con from import:
from paquete.saludos import Saludo Ver carpeta “paquete” y script.py
s = Saludo() script.py
Esta jerarquía se puede expandir tanto como queramos creando subpaquetes, pero siempre
añadiendo el fichero init en cada uno de ellos:
script.py
paquete/
__init__.py
adios/
__init__.py
despedidas.py
hola/
__init__.py
saludos.py
Este es el contenido de paquete/hola/saludos.py
def saludar():
print("Hola, te estoy saludando desde la función saludar() " \
"del módulo saludos")
class Saludo():
def __init__(self):
print("Hola, te estoy saludando desde el __init__" \
"de la clase Saludo")
Este es el contenido de paquete/adios/despedidas.py
def despedir():
print("Adiós, me estoy despidiendo desde la función despedir() " \
"del módulo despedidas")
class Despedida():
def __init__(self):
print("Adiós, me estoy despidiendo desde el __init__ " \
"de la clase Despedida")
Ahora de una forma bien sencilla podemos ejecutar las funciones y métodos de los módulos de
cada subpaquete desde el archivo script.py :
from paquete.hola.saludos import saludar
from paquete.adios.despedidas import Despedida
saludar()
Despedida() script.py
a = list(range(5))
print(a) # [0, 1, 2, 3, 4]
Si por el motivo que fuese requiero crear una lista igual a la anterior, intuitivamente haría lo
siguiente:
b = a
print(b) #[0, 1, 2, 3, 4]
El problema con esta solución es que no estamos creando dos listas con los mismos elementos,
sino que la lista es siempre una y a ella se puede acceder a través de dos nombres diferentes (a y
b). Eso se comprueba sencillamente viendo cómo alterando los elementos de un objeto se refleja
en el otro.
b.append(5)
print(a) #[0, 1, 2, 3, 4, 5]
del a[2] modulo-copy.py
print(b) #[0, 1, 3, 4, 5]
Sumado a que el operador is nos confirma que efectivamente se trata del mismo objeto:
print(a is b) #True
Pero en ocasiones realmente queremos crear dos objetos iguales aunque independientes el uno
del otro, es decir, cada uno con su espacio asignado en la memoria. Allí es donde nos auxilia la
función copy().
from copy import copy
a = list(range(5))
b = copy(a)
print(a is b) #False
b.append(5)
del a[0]
print(a) #[0, 1, 2, 3, 4]
print(b) #[0, 1, 2, 3, 4, 5]
numeros = [1,2,3,4,1,2,3,1,2,1]
print(Counter(numeros)) #Counter({1: 4, 2: 3, 3: 2, 4: 1})
También nos sirve para contar las letras que hay dentro de una palabra:
modulo-collections.py
Para contar las palabras que hay dentro de un conjunto de palabras debemos usar la función
split(), que nos separa ese conjunto de palabras en una lista de palabras:
Fuente: https://estadisticamente.com/modulo-collections-python-contadores/
Módulo datetime
Este módulo contiene las clases time y datetime esenciales para manejar tiempo, horas y fechas.
Clase datetime: Esta clase permite crear objetos para manejar fechas y horas:
from datetime import datetime
terminal
2021-06-29
dt= datetime.now() # Fecha y hora actual
15:56:31.130223
Año: 2021
print(dt) # Imprime fecha y hora actual
Mes: 6
print("Año:", dt.year) # Año
Dia: 29
print("Mes:", dt.month) # Mes
Hora: 15
print("Dia:", dt.day) # Dia
Minuto: 56
Segundo: 31
print("Hora:", dt.hour) # Hora
Microsegundo: 130223
print("Minuto:", dt.minute) # Minuto
15:56:31
print("Segundo:", dt.second) # Segundo
29/6/2021
print("Microsegundo:", dt.microsecond) # Microsegundo
modulo-datetime.py
Es posible crear un datetime manualmente pasando los parámetros (year, month, day, hour=0,
minute=0, second=0, microsecond=0, tzinfo=None). Sólo son obligatorios el año, el mes y el día.
dt = dt.replace(year=3000)
print(dt) #3000-09-28 11:23:00
Mezclas
Y para mezclar colecciones:
# Barajar una lista, queda guardado [2, 4, 3, 1, 5] terminal
lista = [1,2,3,4,5]
random.shuffle(lista)
print(lista)