Mostrando entradas con la etiqueta errores. Mostrar todas las entradas
Mostrando entradas con la etiqueta errores. Mostrar todas las entradas

17 de mayo de 2016

27. Propagación de errores y validaciones

Propagación de errores (raise)

Cuando se produce algún tipo de error, se puede actuar de muchas formas, según el caso. Se puede informar al usuario mediante algún mensaje, se puede mostrar información del contexto en el que se produjo, el tipo de error, etc. para que otro programador lo tenga en cuenta si tiene que modificar o corregir el programa o...
... Se puede hacer que, si la excepción se produjo dentro de la llamada a una función, el error se propague al código que realizó la llamada. Sería algo así como "pasarle el marrón" a quien lo produjo. Por ejemplo, en el siguiente código, el método raise serviría para informar de forma "personalizada" que al dividir entre cero se ha producido un error de división.
# *-* coding:utf-8 *-*

def divide(dividendo, divisor):
    try:
        resultado =  dividendo/divisor
        return resultado
        
    except ZeroDivisionError:
         raise ZeroDivisionError("El divisor no puede ser cero")    
    
divisor = int(raw_input("Divisor (escribe cero): "))
dividendo = int(raw_input("Dividendo: "))

print divide(dividendo, divisor)

= = =
Quizás quede más claro en el siguiente ejemplo...
  1. El programa principal llama a la función f
  2. En la función f se llama a la función g
  3. En g, se llama la h.
  4. En h se levanta una excepción ValueError.
  5. Esta excepción vuelve a g.
  6. Esta excepción retorna a f
  7. Y en f ya se retorna, imprimiendo el texto que se le dio en la función h.
# *-* coding:utf-8 *-*

# Función F
def f():
    try:
        g()
    except ValueError, detalle:
        print "Caught a ValueError:", detalle.message

#Función G
def g(): 
    h()

# Función H
def h():
    raise ValueError('Esta es una prueba.')

# Programa principal
f()
= = =

Validaciones

Las validaciones son técnicas de programación defensiva, que intentan comprobar sobre todo pre-condiciones en las funciones, y devolver información valiosa si no se cumplen.
Se usan par comprobar el tipo de un dato, si está dentro de un determinado rango (o dominio) o que el dato tenga una determinada característica. La validación tiene que informar convenientemente del error. En cualquier caso, lo importante es que el resultado generado por nuestro código cuando funciona correctamente y el resultado generado cuando falla debe ser claramente distinto.
Comprobaciones de dominio o rango, o de contenido
Intentamos que información externa, entregada por el usuario o recuperada de un fichero, sea adecuada, o bien porque pueda dudarse de que lo sea o no. No siempre es fácil. Por ejemplo:
# *-* coding: utf-8 *-*

def raiz(n):
    """
    pre: acepta un número mayor que cero
    post: devuelve un número que es la raíz cuadrada.
    """
    assert n>=0, "No puedo aceptar valores negativos"
    resultado = n ** (0.5)
    return resultado
    
valor = int(raw_input("Valor a calcular la raíz cuadrada: "))
print raiz(valor)
= = =
Validando datos del usuario
No podemos suponer que los usuarios introduzcan correctamente los datos. Es, pues, deseable programar para evitar circunstancias como estas
# *-* coding: utf-8 *-*

def raiz(n):
    """
    pre: acepta un número mayor que cero
    post: devuelve un número que es la raíz cuadrada.
    """
    assert n>=0, "No puedo aceptar valores negativos"
    resultado = n ** (0.5)
    return resultado
    
def leer_dato():
    """
    post: Debe ingresar un número entero. Al usuario se le 
    pedirá un número entero de forma indefinida.
    """
    while True:
        valor = raw_input("Valor a calcular la raíz cuadrada: ")
        try:
            valor = int(valor)
            return valor
        except ValueError:
            print "ATENCIÓN: Debe ingresar un número entero."

valor = leer_dato()
print raiz(valor)
Y un ejemplo en el que sólo se deja al usuario introducir el dato un número limitado de veces.
...
def leer_dato():
    """
    post: Debe ingresar un número entero. Al usuario se le 
    pedirá un número entero una cantidad de veces limitada.
    """
    t = 1
    while t<=3:
        valor = raw_input("Valor a calcular la raíz cuadrada: ")
        try:
            valor = int(valor)
            return valor
        except ValueError:
            t += 1
    raise ValueError, "No has introducido un dato numérico"
...
= = =
Comprobaciones de tipo
Para comprobar si una variable es de un tipo determinado se utiliza la función type, que devuelve el tipo de datos (entero, float, cadena, etc.) de la variable.
if type(i) != int:
    raise TypeError, "i debe ser del tipo int"Si el tipo de datos no es entero, se propaga un error.
Si el tipo de datos se quisiera comprobar que pertenece a más de un tipo puede hacerse
if type(i) not in (int, float, long, complex):
    raise TypeError, "i debe ser numérico"
O, incluso mejor
if not isinstance(i, (int, float, long, complex) ):
    raise TypeError, "i debe ser numérico"
= = =
Comprobaciones de características
No entraremos en muchos detalles. Simplemente saber que la función hasattr(variable, función) vale true si a la variable se le puede aplicar la función (por ejemplo, si variable es un número, entonces se le puede aplicar una función numérica, pero si es de cadena no).
= = =

26. Errores y manejo de excepciones

¿Qué son los errores?

A pesar de lo que alguno pueda pensar, los ordenadores no son ni mucho menos infalibles. Cometen errores, y a todos los niveles. En la transmisión digital, es común la pérdida de información por errores, pero también hay procedimientos que permiten la restauración del código perdido. Es una característica de la digitalización el poder restaurar ciertos errores, sobre todos los relacionados con el ruido.
A nivel de software, nuestros programas tendrán errores.  Algunos evitables, otros inevitables pero tratables. Muchas veces, parte de nuestro código simplemente se escribe para evitar cualquier tipo de error. Y es muy fácil hacer caer a nuestro programa en un error.
Tipos de errores.
  1. Los más fáciles de solucionar son los de sintaxis. El código no se ejecuta porque está mal escrito. Si tenemos un buen editor (IDE) nos irá indicando estos errores, o al ejecutar el código se nos indicará dónde está el error. Es cuestión de escribirlo bien. (En Ninja-ide el código se subraya en rojo, en Geany al compilar o ejecutar indican esos errores).
  2. Error semántico: aunque no tiene errores sintácticos, el programa no hace lo que se desea. Le falta una línea de código, un algoritmo incorrecto...
  3. Error de ejecución o excepciones: aparecen una vez se ejecuta el programa y a éste le ponemos en una situación que no se esperaba, por ejemplo:
    1. Se ha provocado que se divida un número entre 0.
    2. Hemos introducido una cadena de caracteres cuando se pedía un número.
    3. Se accede al lugar 5 de una lista con sólo 3 elementos.
    4. Se intenta leer un archivo que está dañado...
    5. Etc.
Los errores de tipo 1 y 2 son los más fáciles de solucionar, ya que lo que hay que hacer es escribir bien el código o los algoritmos. Los errores del tercer tipo son más difíciles de delimitar, porque su origen puede ser muy complejo. Veremos, a partir de ahora, cómo tratar este tipo de errores.

= = =

Manejo de excepciones

Hemos de tener en cuenta que cuando se produce un error de ejecución o excepción, puede haberse producido dentro de una función o en el programa principal.
Si el error se produce dentro de la función, éste se propaga hacia el código desde donde fue invocada, que puede ser otra función o el programa principal. Si el error no es tratado, y llega al programa principal, se produce la interrupción del mismo.
En el caso de Python, el manejo de excepciones se realiza mediante tres instrucciones: try, except y finally, constituyendo un bloque. 'try' se usa para evaluar un conjunto de instrucciones, 'except' se usa para capturar la excepción y actuar sobre ella o informar al usuario y 'finally' es el código que se ejecuta tras la evaluación de la excepción o del bloque try sin errores (finally no siempre está presente y también es posible tener un bloque finally sin except).
Los bloques except pueden ser genéricos o específicos del error que tratan, y tener más de uno en un bloque.
Por ejemplo, ejecuta este código:
# *-* coding:utf-8 *-*

divisor = 0
dividendo = 200

print dividendo / divisor
Clarísimamente, se generará un error de división entre cero. Podemos ejecutar pues, este código alternativo
# *-* coding:utf-8 *-*

try:
    divisor = int(raw_input("Divisor (escribe cero): "))
    dividendo = int(raw_input("Dividendo: "))
    print dividendo/divisor

except ZeroDivisionError:
    print "Por favor, no sé dividir entre cero"
    
except:
    print "Se ha producido otro tipo de error"
= = =
Para saber más sobre qué error se ha producido , se puede usar el siguiente programa o se puede utilizar la función exc_info del módulo sys (los módulos los veremos más adelante).
# *-* coding:utf-8 *-*

try:
    divisor = int(raw_input("Divisor (escribe cero): "))
    dividendo = int(raw_input("Dividendo: "))
    print dividendo/divisor

except ZeroDivisionError:
    print "Por favor, no sé dividir entre cero"
    
except Exception, ex:
    print "Se ha producido otro tipo de error: " + str(ex)
# *-* coding:utf-8 *-*

import sys

try:
    divisor = int(raw_input("Divisor (escribe cero): "))
    dividendo = int(raw_input("Dividendo: "))
    print dividendo/divisor

except ZeroDivisionError:
    print "Por favor, no sé dividir entre cero"
    
except:
    for i in range(3):
        print("Error inesperado:", sys.exc_info()[i])