8. Errores y excepciones
************************

Hasta ahora los mensajes de error apenas habían sido mencionados, pero
si has probado los ejemplos anteriores probablemente hayas visto
algunos.  Hay (al menos) dos tipos diferentes de errores: *errores de
sintaxis* y *excepciones*.


8.1. Errores de sintaxis
========================

Los errores de sintaxis, también conocidos como errores de
interpretación, son quizás el tipo de queja más común que tenés cuando
todavía estás aprendiendo Python:

   >>> while True print('Hello world')
     File "<stdin>", line 1
       while True print('Hello world')
                      ^
   SyntaxError: invalid syntax

El intérprete reproduce la línea responsable del error y muestra una
pequeña 'flecha' que apunta al primer lugar donde se detectó el error.
El error ha sido provocado (o al menos detectado) en el elemento que
*precede* a la flecha: en el ejemplo, el error se detecta en la
función "print()", ya que faltan dos puntos ("':'") antes del mismo.
Se muestran el nombre del archivo y el número de línea para que sepas
dónde mirar en caso de que la entrada venga de un programa.


8.2. Excepciones
================

Incluso si una declaración o expresión es sintácticamente correcta,
puede generar un error cuando se intenta ejecutar.  Los errores
detectados durante la ejecución se llaman *excepciones*, y no son
incondicionalmente fatales: pronto aprenderás a gestionarlos en
programas Python.  Sin embargo, la mayoría de las excepciones no son
gestionadas por el código, y resultan en mensajes de error como los
mostrados aquí:

   >>> 10 * (1/0)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   ZeroDivisionError: division by zero
   >>> 4 + spam*3
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   NameError: name 'spam' is not defined
   >>> '2' + 2
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: Can't convert 'int' object to str implicitly

La última línea de los mensajes de error indica qué ha sucedido.  Hay
excepciones de diferentes tipos, y el tipo se imprime como parte del
mensaje: los tipos en el ejemplo son: "ZeroDivisionError", "NameError"
y "TypeError".  La cadena mostrada como tipo de la excepción es el
nombre de la excepción predefinida que ha ocurrido.  Esto es válido
para todas las excepciones predefinidas del intérprete, pero no tiene
por que ser así para excepciones definidas por el usuario (aunque es
una convención útil).  Los nombres de las excepciones estándar son
identificadores incorporados al intérprete (no son palabras clave
reservadas).

El resto de la línea provee información basado en el tipo de la
excepción y qué la causó.

La parte anterior del mensaje de error muestra el contexto donde ha
sucedido la excepción, en formato de *traza de error*. En general,
contiene una traza de error que lista líneas de código fuente; sin
embargo, no mostrará líneas leídas desde la entrada estándar.

Built-in Exceptions lista las excepciones predefinidas y sus
significados.


8.3. Gestionando Excepciones
============================

Es posible escribir programas que gestionen determinadas excepciones.
Mirá el siguiente ejemplo, que le pide al usuario una entrada hasta
que ingrese un entero válido, pero permite al usuario interrumpir el
programa (usando "Control-C" o lo que sea que el sistema operativo
soporte); notá que una interrupción generada por el usuario se
señaliza generando la excepción "KeyboardInterrupt".

   >>> while True:
   ...     try:
   ...         x = int(input("Please enter a number: "))
   ...         break
   ...     except ValueError:
   ...         print("Oops!  That was no valid number.  Try again...")
   ...

La declaración "try" funciona de la siguiente manera:

* Primero, se ejecuta la cláusula *try* (la(s) linea(s) entre las
  palabras reservadas "try" y la "except").

* Si no ocurre ninguna excepción, la cláusula *except* se omite y la
  ejecución de la cláusula "try" finaliza.

* Si ocurre una excepción durante la ejecución de la cláusula *try* el
  resto de la cláusula se omite. Entonces, si el tipo de excepción
  coincide con la excepción indicada después de la "except", la
  cláusula *except* se ejecuta, y la ejecución continua después de la
  "try".

* Si ocurre una excepción que no coincide con la indicada en la
  cláusula *except* se pasa a los "try" más externos; si no se
  encuentra un gestor, se genera una *unhandled exception* (excepción
  no gestionada) y la ejecución se interrumpen con un mensaje como el
  que se muestra arriba.

A "try" statement may have more than one except clause, to specify
handlers for different exceptions.  At most one handler will be
executed. Handlers only handle exceptions that occur in the
corresponding try clause, not in other handlers of the same "try"
statement.  An except clause may name multiple exceptions as a
parenthesized tuple, for example:

   ... except (RuntimeError, TypeError, NameError):
   ...     pass

Una clase en una cláusula "except" es compatible con una excepción si
la misma está en la misma clase o una clase base de la misma (pero no
de la otra manera --- una clausula *except* listando una clase
derivada no es compatible con una clase base). Por ejemplo, el
siguiente código imprimirá B, C y D, en ese orden:

   class B(Exception):
       pass

   class C(B):
       pass

   class D(C):
       pass

   for cls in [B, C, D]:
       try:
           raise cls()
       except D:
           print("D")
       except C:
           print("C")
       except B:
           print("B")

Nótese que si las cláusulas *except* estuvieran invertidas (con
"except B" primero), habría impreso B, B, B --- se usa la primera
cláusula *except* coincidente.

El último "except" puede omitir el nombre de la excepción capturada y
servir como comodín.  Usá esto con extremo cuidado, ya que de esta
manera es fácil ocultar un error real de programación.  También puede
usarse para mostrar un mensaje de error y luego re-generar la
excepción (permitiéndole al que llama, gestionar también la
excepción):

   import sys

   try:
       f = open('myfile.txt')
       s = f.readline()
       i = int(s.strip())
   except OSError as err:
       print("OS error: {0}".format(err))
   except ValueError:
       print("Could not convert data to an integer.")
   except:
       print("Unexpected error:", sys.exc_info()[0])
       raise

Las declaraciones "try" ... "except" tienen un *bloque else* opcional,
el cual, cuando está presente, debe seguir a los *except*. Es útil
para aquel código que debe ejecutarse si el *bloque try* no genera una
excepción.  Por ejemplo:

   for arg in sys.argv[1:]:
       try:
           f = open(arg, 'r')
       except OSError:
           print('cannot open', arg)
       else:
           print(arg, 'has', len(f.readlines()), 'lines')
           f.close()

The use of the "else" clause is better than adding additional code to
the "try" clause because it avoids accidentally catching an exception
that wasn't raised by the code being protected by the "try" ...
"except" statement.

Cuando ocurre una excepción, puede tener un valor asociado, también
conocido como el *argumento* de la excepción.  La presencia y el tipo
de argumento depende del tipo de excepción.

El "except" puede especificar una variable luego del nombre de
excepción.  La variable se vincula a una instancia de excepción con
los argumentos almacenados en "instance.args".  Por conveniencia, la
instancia de excepción define "__str__()" para que se pueda mostrar
los argumentos directamente, sin necesidad de hacer referencia a
".args".  También se puede instanciar la excepción primero, antes de
generarla, y agregarle los atributos que se desee:

   >>> try:
   ...     raise Exception('spam', 'eggs')
   ... except Exception as inst:
   ...     print(type(inst))    # the exception instance
   ...     print(inst.args)     # arguments stored in .args
   ...     print(inst)          # __str__ allows args to be printed directly,
   ...                          # but may be overridden in exception subclasses
   ...     x, y = inst.args     # unpack args
   ...     print('x =', x)
   ...     print('y =', y)
   ...
   <class 'Exception'>
   ('spam', 'eggs')
   ('spam', 'eggs')
   x = spam
   y = eggs

Si una excepción tiene argumentos, estos se imprimen como en la parte
final (el 'detalle') del mensaje para las excepciones no gestionadas
('*Unhandled exception*').

Los gestores de excepciones no gestionan solamente las excepciones que
ocurren en el *bloque try*, también gestionan las excepciones que
ocurren dentro de las funciones que se llaman (inclusive
indirectamente) dentro del *bloque try*.  Por ejemplo:

   >>> def this_fails():
   ...     x = 1/0
   ...
   >>> try:
   ...     this_fails()
   ... except ZeroDivisionError as err:
   ...     print('Handling run-time error:', err)
   ...
   Handling run-time error: division by zero


8.4. Levantando excepciones
===========================

La declaración "raise" permite al programador forzar a que ocurra una
excepción específica.  Por ejemplo:

   >>> raise NameError('HiThere')
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   NameError: HiThere

El único argumento de "raise" indica la excepción a generarse. Tiene
que ser o una instancia de excepción, o una clase de excepción (una
clase que hereda de "Exception"). Si se pasa una clase de excepción,
la misma será instanciada implícitamente llamando a su constructor sin
argumentos:

   raise ValueError  # shorthand for 'raise ValueError()'

Si necesitás determinar si una excepción fue lanzada pero no querés
gestionarla, una versión simplificada de la instrucción "raise" te
permite relanzarla:

   >>> try:
   ...     raise NameError('HiThere')
   ... except NameError:
   ...     print('An exception flew by!')
   ...     raise
   ...
   An exception flew by!
   Traceback (most recent call last):
     File "<stdin>", line 2, in <module>
   NameError: HiThere


8.5. Excepciones definidas por el usuario
=========================================

Los programas pueden nombrar sus propias excepciones creando una nueva
clase excepción (mirá Clases para más información sobre las clases de
Python).  Las excepciones, típicamente, deberán derivar de la clase
"Exception", directa o indirectamente.

Las clases de Excepción pueden ser definidas de la misma forma que
cualquier otra clase, pero es habitual mantenerlas lo más simples
posible, a menudo ofreciendo solo un número de atributos con
información sobre el error que leerán los gestores de la excepción.
Al crear un módulo que puede lanzar varios errores distintos, una
práctica común es crear una clase base para excepciones definidas en
ese módulo y extenderla para crear clases excepciones específicas para
distintas condiciones de error:

   class Error(Exception):
       """Base class for exceptions in this module."""
       pass

   class InputError(Error):
       """Exception raised for errors in the input.

       Attributes:
           expression -- input expression in which the error occurred
           message -- explanation of the error
       """

       def __init__(self, expression, message):
           self.expression = expression
           self.message = message

   class TransitionError(Error):
       """Raised when an operation attempts a state transition that's not
       allowed.

       Attributes:
           previous -- state at beginning of transition
           next -- attempted new state
           message -- explanation of why the specific transition is not allowed
       """

       def __init__(self, previous, next, message):
           self.previous = previous
           self.next = next
           self.message = message

La mayoría de las excepciones se definen con nombres acabados en
"Error", de manera similar a la nomenclatura de las excepciones
estándar.

Muchos módulos estándar definen sus propias excepciones para reportar
errores que pueden ocurrir en funciones propias. Se puede encontrar
más información sobre clases en el capítulo Clases.


8.6. Definiendo Acciones de Limpieza
====================================

La declaración "try" tiene otra cláusula opcional cuyo propósito es
definir acciones de limpieza que serán ejecutadas bajo ciertas
circunstancias. Por ejemplo:

   >>> try:
   ...     raise KeyboardInterrupt
   ... finally:
   ...     print('Goodbye, world!')
   ...
   Goodbye, world!
   Traceback (most recent call last):
     File "<stdin>", line 2, in <module>
   KeyboardInterrupt

A *finally clause* is always executed before leaving the "try"
statement, whether an exception has occurred or not. When an exception
has occurred in the "try" clause and has not been handled by an
"except" clause (or it has occurred in an "except" or "else" clause),
it is re-raised after the "finally" clause has been executed.  The
"finally" clause is also executed "on the way out" when any other
clause of the "try" statement is left via a "break", "continue" or
"return" statement.  A more complicated example:

   >>> def divide(x, y):
   ...     try:
   ...         result = x / y
   ...     except ZeroDivisionError:
   ...         print("division by zero!")
   ...     else:
   ...         print("result is", result)
   ...     finally:
   ...         print("executing finally clause")
   ...
   >>> divide(2, 1)
   result is 2.0
   executing finally clause
   >>> divide(2, 0)
   division by zero!
   executing finally clause
   >>> divide("2", "1")
   executing finally clause
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
     File "<stdin>", line 3, in divide
   TypeError: unsupported operand type(s) for /: 'str' and 'str'

As you can see, the "finally" clause is executed in any event.  The
"TypeError" raised by dividing two strings is not handled by the
"except" clause and therefore re-raised after the "finally" clause has
been executed.

En aplicaciones reales, la cláusula "finally" es útil para liberar
recursos externos (como archivos o conexiones de red), sin importar si
el uso del recurso fue exitoso.


8.7. Acciones predefinidas de limpieza
======================================

Algunos objetos definen acciones de limpieza estándar para llevar a
cabo cuando el objeto ya no necesario, independientemente de que las
operaciones sobre el objeto hayan sido exitosas o no. Véase el
siguiente ejemplo, que intenta abrir un archivo e imprimir su
contenido en la pantalla.

   for line in open("myfile.txt"):
       print(line, end="")

El problema con este código es que deja el archivo abierto por un
periodo de tiempo indeterminado luego de que esta parte termine de
ejecutarse.  Esto no es un problema en *scripts* simples, pero puede
ser un problema en aplicaciones más grandes.  La declaración "with"
permite que los objetoscomo archivos sean usados de una forma que
asegure que siempre se los libera rápido y en forma correcta.:

   with open("myfile.txt") as f:
       for line in f:
           print(line, end="")

Una vez que la declaración se ejecuta, el fichero *f* siempre se
cierra, incluso si aparece algún error durante el procesado de las
líneas.  Los objetos que, como los ficheros, posean acciones
predefinidas de limpieza lo indicarán en su documentación.
