1.2 Intermezzo: Errores y excepciones

Hay una importante convención en todo el intérprete de Python: cuando una función fracasa, debe establecer una condición de excepción y devolver un valor de error (normalmente, un puntero NULL). Las excepciones se almacenan en una variable global estática dentro del intérprete: si esta variable es NULL es que no ha ocurrido ninguna excepción. Una segunda variable global almacena el ``valor asociado'' a la excepción (el segundo argumento de raise). Una tercera variable almacena el trazado de la pila en el caso en que el error se generase en el código Python. Estas tres variables son las equivalentes en C de las variables de Python sys.exc_type, sys.exc_value y sys.exc_traceback (consultar la sección del módulo sys en la Referencia de la biblioteca de Python). Es importante conocerlas para comprender como se transmiten los errores.

El API de Python define varias funciones para establecer los diversos tipos de excepciones.

La más común es PyErr_SetString(). Sus argumentos son un objeto excepción y una cadena C. El objeto excepción suele ser un objeto predefinido, como PyExc_ZeroDivisionError. La cadena C indica la causa del error. Se convierte a objeto cadena Python y se almacena como ``valor asociado'' a la excepción.

Otra función útil es PyErr_SetFromErrno(), que sólo toma un argumento excepción y construye el valor asociado mediante inspección de la variable global errno. La función más general es PyErr_SetObject(), que toma dos argumentos objeto, la excepción y su valor asociado. No hay que aplicar Py_INCREF() a los objetos pasados a ninguna de estas funciones.

Se puede hacer una comprobación no destructiva de si se ha establecido una excepción con PyErr_Occurred(). Ésta devuelve el objeto excepción en curso o NULL si no ha habido excepciones. No se suele tener que llamar a PyErr_Occurred() para ver si hubo un error en una llamada a función, ya que se debería saber por el valor devuelto.

Cuando una función f que llama a otra función g detecta que ésta falla, la misma f debería devolver un valor de error (es decir, NULL o -1). No debería llamar a ninguna de las funciones PyErr_*(), g ya ha llamado a alguna. Se supone que el que llamó a f también devolverá una indicación de error a su llamante, de nuevo sin llamar a PyErr_*() y así sucesivamente. La causa de error más detallada ya fue explicada por la función que la detectó primero. Al llegar el error al bucle principal de Python, interrumpe la ejecución del código Python e intenta encontrar un gestor de excepciones especificado por el programador en Python.

Hay situaciones en las que un módulo realmente puede dar un mensaje de error más detallado llamando a otra función PyErr_*() y, en dichos casos, es adecuado hacerlo. De forma general, no obstante, no es necesario y puede causar pérdida de información de la causa del error: la mayoría de las operaciones pueden fracasar por multitud de razones.

Para hacer caso omiso de una excepción establecida por una llamada a función fracasada, la condición de excepción debe restablecerse explícitamente llamando a PyErr_Clear(). El único momento en que el código C debería llamar a PyErr_Clear() es si no desea transmitir el error sino gestionarlo él completamente (por ejemplo, intentando otra cosa o fingir que no ha ocurrido nada).

Toda llamada sin éxito a malloc() debe convertirse en una excepción. El que llamó a malloc() (o a realloc()) debe llamar a PyErr_NoMemory() y devolver un indicador de fracaso. Todas las funciones de creación de objetos (por ejemplo, PyInt_FromLong()) ya lo hacen, por lo que esta nota sólo afecta a aquéllos que llamen a malloc() directamente.

Hay que destacar también que , con la importante excepción de PyArg_ParseTuple() y similares, las funciones que devuelven un entero de estado suelen devolver un valor positivo o cero para éxito y -1 para fracaso, como las llamadas al sistema en Unix.

Finalmente, ¡hay que tener cuidado de limpiar la basura (llamando a Py_XDECREF() o a Py_DECREF() para los objetos que ya han sido creados) al devolver un indicador de error!

La excepción que se lanza queda a elección del programador. Hay objetos C predeclarados correspondientes a todas las excepciones internas de Python, por ejemplo PyExc_ZeroDivisionError, directamente utilizables. Por supuesto, se deben usar las excepciones sabiamente, no se vaya a usar PyExc_TypeError para indicar que un fichero no se ha podido abrir (lo que se debería indicar probablemente con PyExc_IOError). Si hay algo incorrecto en la lista de argumentos, la función PyArg_ParseTuple() suele lanzar PyExc_TypeError. Si hay un argumento cuyo valor está restringido a un rango o debe satisfacer ciertas condiciones, lo apropiado es lanzar PyExc_ValueError.

También se puede definir una excepción exclusiva del módulo. Para ello, se suele declarar una variable objeto estática al inicio del fichero. Por ejemplo:

static PyObject *SpamError;

y se inicializa en la función de inicialización del módulo (initspam()) con un objeto excepción, por ejemplo, y dejando aparte la comprobación de errores de momento:

void
initspam()
{
    PyObject *m, *d;

    m = Py_InitModule("spam", SpamMethods);
    d = PyModule_GetDict(m);
    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    PyDict_SetItemString(d, "error", SpamError);
}

Es importante hacer ver que el nombre en Python del objeto excepción es spam.error. La función PyErr_NewException() puede crear tanto una cadena como una clase, dependiendo de si se pasó el indicador -X al intérprete. Si se puso -X, SpamError será un objeto cadena; en caso contrario será un objeto de clase cuya clase base será Exception, descrita en la Referecia de la biblioteca de Python en ``Excepciones internas''.


Ver Sobre este documento... para obtener información sobre sugerencias.