Las validaciones son técnicas que permiten asegurar que los valores con los que se vaya a operar estén dentro de determinado dominio.

Estas técnicas son particularmente importantes al momento de utilizar entradas del usuario o de un archivo (o entradas externas en general) en nuestro código, y también se las utiliza para comprobar precondiciones. Al uso intensivo de estas técnicas se lo suele llamar programación defensiva.

Si bien quien invoca una función debe preocuparse de cumplir con las precondiciones de ésta, si las validaciones están hechas correctamente pueden devolver información valiosa para que el invocante pueda actuar en consecuencia.

Hay distintas formas de comprobar el dominio de un dato. Se puede comprobar el contenido; que una variable sea de un tipo en particular; o que el dato tenga determinada característica, como que deba ser "comparable", o "iterable".

También se debe tener en cuenta qué hará nuestro código cuando una validación falle, ya que queremos darle información al invocante que le sirva para procesar el error. El error producido tiene que ser fácilmente reconocible. En algunos casos, como por ejemplo cuando se quiere devolver una posición, devolver -1 nos puede asegurar que el invocante lo vaya a reconocer. En otros casos, levantar una excepción es una solución más elegante.

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. Por ejemplo, si el código debe devolver un elemento de una secuencia, no es una buena idea que devuelva None en el caso de que la secuencia esté vacía, ya que None es un elemento válido dentro de una secuencia.

12.3.1. Comprobaciones por contenido

Cuando queremos validar que los datos provistos a una porción de código contengan la información apropiada, ya sea porque esa información la ingresó un usuario, fue leída de un archivo, o porque por cualquier motivo es posible que sea incorrecta, es deseable comprobar que el contenido de las variables a utilizar estén dentro de los valores con los que se puede operar.

Estas comprobaciones no siempre son posibles, ya que en ciertas situaciones puede ser muy costoso corroborar las precondiciones de una función. Es por ello que este tipo de comprobaciones se realizan sólo cuando sea posible.

Por ejemplo, la función factorial está definida para los números naturales incluyendo el 0. Es posible utilizar assert (que es otra forma de levantar una excepción) para comprobar las precondiciones de factorial.

def factorial(n):
    """ Calcula el factorial de n.
    Pre: n debe ser un entero, mayor igual a 0
    Post: se devuelve el valor del factorial pedido
    """
    assert n >= 0, "n debe ser mayor igual a 0"
    fact=1
    for i in xrange(2,n+1):
        fact*=i
    return fact

12.3.2. Entrada del usuario

En el caso particular de una porción de código que trate con entrada del usuario, no se debe asumir que el usuario vaya a ingresar los datos correctamente, ya que los seres humanos tienden a cometer errores al ingresar información.

Por ejemplo, si se desea que un usuario ingrese un número, no se debe asumir que vaya a ingresarlo correctamente. Se lo debe guardar en una cadena y luego convertir a un número, es por eso que es recomendable el uso de la función raw_input ya que devuelve una cadena que puede ser procesada posteriormente.

def lee_entero():
    """ Solicita un valor entero y lo devuelve.
        Si el valor ingresado no es entero, lanza una excepción. """
    valor = raw_input("Ingrese un número entero: ")
    return int(valor)

Esta función devuelve un valor entero, o lanza una excepción si la conversión no fue posible. Sin embargo, esto no es suficiente. En el caso en el que el usuario no haya ingresado la información correctamente, es necesario volver a solicitarla.

def lee_entero():
    """ Solicita un valor entero y lo devuelve.
        Mientras el valor ingresado no sea entero, vuelve a solicitarlo. """
    while True:
        valor = raw_input("Ingrese un número entero: ")
        try:
            valor = int(valor)
            return valor
        except ValueError:
            print "ATENCIÓN: Debe ingresar un número entero."

Podría ser deseable, además, poner un límite a la cantidad máxima de intentos que el usuario tiene para ingresar la información correctamente y, superada esa cantidad máxima de intentos, levantar una excepción para que sea manejada por el código invocante.

def lee_entero():
    """ Solicita un valor entero y lo devuelve.
        Si el valor ingresado no es entero, da 5 intentos para ingresarlo
        correctamente, y de no ser así, lanza una excepción. """
    intentos = 0
    while intentos < 5:
        valor = raw_input("Ingrese un número entero: ")
        try:
            valor = int(valor)
            return valor
        except ValueError:
            intentos += 1
    raise ValueError, "Valor incorrecto ingresado en 5 intentos"

Por otro lado, cuando la entrada ingresada sea una cadena, no es esperable que el usuario la vaya a ingresar en mayúsculas o minúsculas, ambos casos deben ser considerados.

def lee_opcion():
    """ Solicita una opción de menú y la devuelve. """
    while True:
        print "Ingrese A (Altas) - B (Bajas) - M (Modificaciones): ",
        opcion = raw_input().upper()
        if opcion in ["A", "B", "M"]:
            return opcion

12.3.3. Comprobaciones por tipo

En esta clase de comprobaciones nos interesa el tipo del dato que vamos a tratar de validar, Python nos indica el tipo de una variable usando la función type(variable). Por ejemplo, para comprobar que una variable contenga un tipo entero podemos hacer:

if type(i) != int:
    raise TypeError, "i debe ser del tipo int"

Sin embargo, ya hemos visto que tanto las listas como las tuplas y las cadenas son secuencias, y muchas de las funciones utilizadas puede utilizar cualquiera de estas secuencias. De la misma manera, una función puede utilizar un valor numérico, y que opere correctamente ya sea entero, flotante, o complejo.

Es posible comprobar el tipo de nuestra variable contra una secuencia de tipos posibles.

if type(i) not in (int, float, long, complex):
    raise TypeError, "i debe ser numérico"

Si bien esto es bastante más flexible que el ejemplo anterior, también puede ser restrictivo ya que - como se verá más adelante - cada programador puede definir sus propios tipos utilizando como base los que ya están definidos. Con este código se están descartando todos los tipos que se basen en int, float, long o complex.

Para poder incluir estos tipos en la comprobación a realizar, Python nos provee de la función isinstance(variable, tipos).

if not isinstance(i, (int, float, long, complex) ):
    raise TypeError, "i debe ser numérico"

Con esto comprobamos si una variable es de determinado tipo o subtipo de éste. Esta opción es bastante flexible, pero existen aún más opciones.

Advertencia Hacer comprobaciones sobre los tipos de las variables suele resultar demasiado restrictivo, ya que es muy posible que una porción de código que opere con un tipo en particular funcione correctamente con otros tipos de variables que se comporten de forma similar.

Es por eso que hay que tener mucho cuidado al limitar el uso de una variable por su tipo, y en muchos casos es preferible limitarlas por sus propiedades, como el ejemplo anterior, en que se requería que se pudiera convertir a un entero.

Para la mayoría de los tipos básicos de Python existe una función que se llama de la misma manera que el tipo que devuelve un elemento de ese tipo, por ejemplo, int() devuelve 0, dict() devuelve {} y así. Además, estas funciones suelen poder recibir un elemento de otro tipo para tratar de convertirlo, por ejemplo, int(3.0) devuelve 3, list("Hola") devuelve ['H', 'o', 'l', 'a'].

Usando está conversión conseguimos dos cosas: podemos convertir un tipo recibido al que realmente necesitamos, a la vez que tenemos una copia de este, dejando el original intacto, que es importante cuando estamos tratando con tipos mutables.

Por ejemplo, si se quiere contar con una función de división entera que pueda recibir diversos parámetros, podría hacerse de la siguiente manera.

def division_entera(x,y):
    """ Calcula la división entera después de convertir los parámetros a
    enteros. """
    try:
        dividendo = int(x)
        divisor = int(y)
        return dividendo/divisor
    except ValueError:
        raise ValueError, "x e y deben poder convertirse a enteros"
    except ZeroDivisionError:
        raise ZeroDivisionError, "y no puede ser cero"

De esta manera, la función division_entera puede ser llamada incluso con cadenas que contengan expresiones enteras. Que este comportamiento sea deseable o no, depende siempre de cada caso.

12.3.4. Comprobaciones por características

Otra posible comprobación, dejando de lado los tipos, consiste en verificar si una variable tiene determinada característica o no. Python promueve este tipo de programación, ya que el mismo intérprete utiliza este tipo de comprobaciones. Por ejemplo, para imprimir una variable, Python convierte esa variable a una cadena, no hay en el interprete una verificación para cada tipo, sino que busca una función especial, llamada __str__, en la variable a imprimir, y si existe, la utiliza para convertir la variable a una cadena.

Nota Python utiliza la idea de duck typing, que viene del concepto de que si algo parece un pato, camina como un pato y grazna como un pato, entonces, se lo puede considerar un pato.

Esto se refiere a no diferenciar las variables por los tipos a los que pertenecen, sino por las funciones que tienen.

Para comprobar si una variable tiene o no una función Python provee la función hasattr(variable, atributo), donde atributo puede ser el nombre de la función o de la variable que se quiera verificar. Se verá más sobre atributos en la unidad de Programación Orientada a Objetos.

Por ejemplo, existe la función __add__ para realizar operaciones de suma entre elementos. Si se quiere corroborar si un elemento es sumable, se lo haría de la siguiente forma.

if not hasattr(i,"__add__"):
    raise TypeError, "El elemento no es sumable"

Sin embargo, que el atributo exista no quiere decir que vaya a funcionar correctamente en todos los casos. Por ejemplo, tanto las cadenas como los números definen su propia "suma", pero no es posible sumar cadenas y números, de modo que en este caso sería necesario tener en cuenta una posible excepción.

Por otro lado, en la mayoría de los casos se puede aplicar la frase: es más fácil pedir perdón que permiso, atribuída a la programadora Grace Hopper. Es decir, en este caso es más sencillo hacer la suma dentro de un bloque try y manejar la excepción en caso de error, que saber cuáles son los detalles de la implementación de __add__ de cada tipo interactuante.


Copyright (c) 2011-2014 Rosita Wachenchauzer, Margarita Manterola, Maximiliano Curia, Marcos Medrano, Nicolás Paez. La copia y redistribución de esta página se permite bajo los términos de la licencia Creative Commons Atribución - Compartir Obras Derivadas Igual 3.0 siempre que se conserve esta nota de copyright.