Errores y excepciones de Python3

Como principiante en Python, al comenzar a aprender programación en Python, a menudo verás mensajes de error. No los hemos mencionado anteriormente; en este capítulo los presentaremos específicamente.

Python tiene dos tipos de errores fáciles de reconocer: errores de sintaxis y excepciones.

Python assert (aserción) se utiliza para evaluar una expresión; cuando la condición de la expresión es false, se desencadena una excepción.

Errores de sintaxis

Los errores de sintaxis de Python, también llamados errores de análisis, son comunes entre los principiantes, como en el siguiente ejemplo:

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

En este ejemplo, se detectó un error en la función print(): le faltan dos puntos (:) delante.: 。

El analizador sintáctico señala la línea donde ocurrió el error y marca una pequeña flecha en la posición del primer error encontrado.

Excepciones

Incluso si la sintaxis de un programa Python es correcta, pueden ocurrir errores al ejecutarlo. Los errores detectados en tiempo de ejecución se denominan excepciones.

La mayoría de las excepciones no son manejadas por el programa y se muestran aquí en forma de mensajes de error:

Ejemplos

>>> 10 * (1/0)             # 0 no puede ser divisor, se desencadena una excepción
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: division by zero
>>> 4 + spam*3             # spam no está definido, se desencadena una excepción
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2               # int no se puede sumar con str, se desencadena una excepción
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

Las excepciones aparecen en diferentes tipos, y estos tipos se imprimen como parte de la información: en los ejemplos, los tipos son ZeroDivisionError, NameError y TypeError.

La parte inicial del mensaje de error muestra el contexto donde ocurrió la excepción y presenta la información detallada en forma de pila de llamadas.

Manejo de excepciones

try/except

La captura de excepciones se puede realizar con latry/exceptsentencia.

En el siguiente ejemplo, se pide al usuario que introduzca un entero válido, pero se le permite interrumpir el programa (usando Control-C o el método proporcionado por el sistema operativo). La interrupción del usuario provocará una excepción KeyboardInterrupt.

while True:
    try:
        x = int(input("Por favor, ingresa un número: "))
        break
    except ValueError:
        print("Lo que ingresaste no es un número, ¡vuelve a intentarlo!")

La sentencia try funciona de la siguiente manera:

  • Primero, se ejecuta la cláusula try (las sentencias entre la palabra clave try y la palabra clave except).

  • Si no ocurre ninguna excepción, se ignora la cláusula except, y la cláusula try termina después de ejecutarse.

  • Si ocurre una excepción durante la ejecución de la cláusula try, se ignora el resto de la cláusula try. Si el tipo de la excepción coincide con el nombre después de except, se ejecutará la cláusula except correspondiente.

  • Si una excepción no coincide con ningún except, la excepción se transmitirá al try de nivel superior.

Una sentencia try puede contener varias cláusulas except para manejar diferentes excepciones específicas. Como máximo solo se ejecutará una rama.

Los manejadores solo manejarán las excepciones de la cláusula try correspondiente, no las excepciones de otros manejadores try.

Una cláusula except puede manejar varias excepciones a la vez; estas excepciones se colocan entre paréntesis formando una tupla, por ejemplo:

except (RuntimeError, TypeError, NameError):
    pass

La última cláusula except puede omitir el nombre de la excepción, y se usará como comodín. Puedes usar este método para imprimir un mensaje de error y luego volver a lanzar 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

try/except...else

try/exceptLa sentencia también tiene unaelsecláusula opcional; si se usa esta cláusula, debe colocarse después de todas las cláusulas except.

La cláusula else se ejecutará cuando no haya ocurrido ninguna excepción en la cláusula try.

El siguiente ejemplo determina si un archivo se puede abrir en la sentencia try. Si al abrir el archivo todo es normal y no ocurre ninguna excepción, se ejecuta la parte de else y se lee el contenido del archivo:

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

Usar la cláusula else es mejor que poner todas las sentencias dentro de la cláusula try, porque así se evitan algunas excepciones inesperadas que except no puede capturar.

El manejo de excepciones no solo maneja las excepciones que ocurren directamente en la cláusula try, sino también las excepciones lanzadas en las funciones llamadas desde la cláusula (incluso en funciones llamadas indirectamente). 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: int division or modulo by zero

Sentencia try-finally

La sentencia try-finally ejecutará el código final sin importar si ocurre o no una excepción.

En el siguiente ejemplo, la sentencia finally se ejecutará tanto si ocurre una excepción como si no:

Ejemplos

try:
    ejemplo()
except AssertionError as error:
    print(error)
else:
    try:
        with open('file.log') as file:
            read_data = file.read()
    except FileNotFoundError as fnf_error:
        print(fnf_error)
finally:
    print('Esta frase se ejecutará sin importar si ocurre una excepción o no.')

Lanzar excepciones

Python utiliza la sentencia raise para lanzar una excepción específica.

La sintaxis de raise es la siguiente:

raise [Exception [, args [, traceback]]]

El siguiente ejemplo desencadena una excepción si x es mayor que 5:

x = 10
if x > 5:
    raise Exception('x no puede ser mayor que 5. El valor de x es: {}'.format(x))

Ejecutar el código anterior desencadenará una excepción:

Traceback (most recent call last):
  File "test.py", line 3, in <module>
    raise Exception('x 不能大于 5。x 的值为: {}'.format(x))
Exception: x 不能大于 5。x 的值为: 10

El único parámetro de raise especifica la excepción que se va a lanzar. Debe ser una instancia de excepción o una clase de excepción (es decir, una subclase de Exception).

Si solo quieres saber si se ha lanzado una excepción y no deseas manejarla, una simple sentencia raise puede volver a lanzarla.

>>> try:
        raise NameError('HiThere')  # Simula una excepción.
    except NameError:
        print('An exception flew by!')
        raise
   
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

Excepciones definidas por el usuario

Puedes tener tus propias excepciones creando una nueva clase de excepción. La clase de excepción hereda de la clase Exception, ya sea directa o indirectamente, por ejemplo:

>>> class MyError(Exception):
        def __init__(self, value):
            self.value = value
        def __str__(self):
            return repr(self.value)
   
>>> try:
        raise MyError(2*2)
    except MyError as e:
        print('My exception occurred, value:', e.value)
   
My exception occurred, value: 4
>>> raise MyError('oops!')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

En este ejemplo, el __init__() predeterminado de la clase Exception se sobrescribe.

Cuando se crea un módulo que puede lanzar varios tipos diferentes de excepciones, una práctica habitual es establecer una clase de excepción base para el paquete y, basándose en esta clase base, crear diferentes subclases para distintas situaciones 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 los nombres de las excepciones terminan en 'Error', igual que la nomenclatura estándar de excepciones.


Definir acciones de limpieza

La sentencia try tiene otra cláusula opcional que define una acción de limpieza que se ejecutará en cualquier caso. Por ejemplo:

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

En el ejemplo anterior, la cláusula finally se ejecutará sin importar si ocurrió una excepción en la cláusula try.

Si se lanza una excepción en la cláusula try (o en las cláusulas except y else) y ningún except la captura, dicha excepción se lanzará después de ejecutarse la cláusula finally.

A continuación se muestra un ejemplo más complejo (que incluye cláusulas except y finally en la misma sentencia try):

>>> 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 ?
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

Acciones de limpieza predefinidas

Algunos objetos definen acciones de limpieza estándar. Una vez que el objeto ya no es necesario, se ejecuta esta acción de limpieza estándar, sin importar si el sistema lo utilizó con éxito o no.

El siguiente ejemplo muestra cómo intentar abrir un archivo y luego imprimir su contenido en pantalla:

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

El problema del código anterior es que, después de ejecutarse, el archivo permanece abierto y no se cierra.

La sentencia con la palabra clave with garantiza que objetos como los archivos ejecutarán correctamente su método de limpieza después de su uso:

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

Después de ejecutar el código anterior, incluso si surge un problema durante el proceso, el archivo f siempre se cerrará.

Para más contenido sobre la palabra clave with, consulta:La palabra clave with en Python


Contenido relacionado

Python assert (aserción)

La palabra clave with en Python

Otras extensiones