# Excepciones en Python3

## <mark style="color:blue;">1. ¿Qué son las excepciones?</mark>[¶](http://localhost:8888/notebooks/curso_python/Excepciones_blank.ipynb#1.-%C2%BFQu%C3%A9-son-las-excepciones?) <a href="#id-1.-que-son-las-excepciones" id="id-1.-que-son-las-excepciones"></a>

Python utiliza un tipo de objeto especial denominado excepción para gestionar los errores que surgen durante la ejecución de un programa.

Cada vez que ocurre un error que hace que Python no sepa qué hacer a continuación, crea un objeto de excepción. Si se escribe código que maneja la excepción, el programa continuará ejecutándose. Por el contrario, si no se maneja la excepción, el programa se detendrá y mostrará un pequeño resumen de la excepción que se ha producido.

Las excepciones se pueden manejan a través de las sentencias `try` y `except`.

```python
print(var)

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[1], line 1
----> 1 print(var)

NameError: name 'var' is not defined
```

## <mark style="color:blue;">2. Errores de sintaxis vs Excepciones</mark>

A pesar de que en muchas ocasiones estos dos términos se utilizan de manera indistinta cuando hablamos de Python, debemos tener cuidado porque son cosas diferentes.

Los **errores de sintaxis** se producen cuando escribimos una sentencia de código en Python que no es sintácticamente válida. El intérprete de Python indica estos error con el término `SyntaxError` y nos señala con el carácter `^` donde se encuentra el error.

```python
print("Hola mundo)

  Cell In[2], line 1
    print("Hola mundo)
          ^
SyntaxError: unterminated string literal (detected at line 1)
```

**Este tipo de errores no se pueden controlar y no se corresponden con excepciones dentro del lenguaje**

Por otro lado, las **excepciones** se corresponden con errores que se producen en sentencias de código en Python que son sintácticamente correctas. Esto tipo de errores no son fatales para el programa y pueden ser gestionados o ignorados.

```python
print(var)

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[3], line 1
----> 1 print(var)

NameError: name 'var' is not defined
```

```python
50/0

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[4], line 1
----> 1 50/0

ZeroDivisionError: division by zero
```

```python
'2' + 2

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[5], line 1
----> 1 '2' + 2

TypeError: can only concatenate str (not "int") to str
```

## <mark style="color:blue;">3. Gestión de excepciones: Sentencias</mark> <mark style="color:blue;"></mark><mark style="color:blue;">`try`</mark> <mark style="color:blue;"></mark><mark style="color:blue;">y</mark> <mark style="color:blue;"></mark><mark style="color:blue;">`except`</mark>

Las sentencias `try` y `except` en Python pueden utilizarse para capturar y manejar excepciones. Python ejecuta el código que sigue a la sentencia `try` como una parte "normal" del programa. El código que sigue a la sentencia `except` se ejecutará si se produce cualquier excepción en la cláusula `try` precedente.

La sintaxis que se utiliza para definir este tipo de comportamiento es la siguiente:

```python
try:
    <sentencia(s)>
except <excepción>:
    <sentencias(s) si excepción>
```

```python
print(var)

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 1
----> 1 print(var)

NameError: name 'var' is not defined
```

```python
try:
    print(var)
except NameError:
    var = "Hola mundo"
print(var)

Hola mundo
```

Debemos tener en cuenta que el las sentencias de código que se encuentren en el cuerpo de la sentencia `try` y a continuación de la sentencia que emite la excepción, no se ejecutarán

```python
print("...SNIP...")

try:
    print("Código antes de la excepción")
    10 + '3'
    print("Código despues de la excepción")
except TypeError:
    print("[!] No se puede sumar un número entero y un string")
    
print("...SNIP...")

...SNIP...

Código antes de la excepción
[!] No se puede sumar un número entero y un string

...SNIP...
```

También podemos utilizar la sentencia `except` sin indicarle el nombre de ninguna excepción, en estos casos capturará todas las excepciones que se produzcan en el código que se encuentra en el cuerpo de la sentencia `try`

```python
print("...SNIP...")

try:
    10 + '3'
except:
    print("[!] No se puede sumar un número entero y un string")
    
print("...SNIP...")

...SNIP...

[!] No se puede sumar un número entero y un string

...SNIP...
```

Adicionalmente a la sintaxis anterior, podemos capturar varias excepciones de manera simultánea utilizando varias cláusulas `except`

```python
try:
    print(50/0) # Error de ZeroDivisionError
except NameError:
    print("[+] Gestionando excepcion NameError")
except TypeError:
    print("[+] Gestionando excepcion TypeError")
except ZeroDivisionError:
    print("[+] No puedes dividir un número por 0")
    
[+] No puedes dividir un número por 0
```

Por último, podemos asignar el objeto excepción capturado a una variable y utilizarlos para mostrar más información al respecto.

```python
try:
    print(variable)
except NameError as error:
    print("[*] Objeto de tipo:", type(error))
    print("[!] La excepción consiste en:", error)
    
[*] Objeto de tipo: <class 'NameError'>
[!] La excepción consiste en: name 'variable' is not defined
```

## <mark style="color:blue;">4. Lanzando excepciones personalizadas</mark>

Además de las sentencias anteriores que podemos utilizar para controlar excepciones, Python nos proporciona la sentencia `raise` con la que podemos emitir nuestras propias excepciones. Para ello, debemos utilizar la clase por defecto de Python `Exception`

```python
help(Exception)
```

```python
colores_permitidos = ("azul", "verde", "amarillo", "rojo")
color = "morado"
if color not in colores_permitidos:
    raise Exception("[!] El color {} no está en la lista de colores permitidos".format(color))
    
# Al ejecutar este código nos devuelve una Exception personalizada
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
Cell In[21], line 6
      3 color = "morado"
      5 if color not in colores_permitidos:
----> 6     raise Exception("[!] El color {} no está en la lista de colores permitidos".format(color))

Exception: [!] El color morado no está en la lista de colores permitidos
```

## <mark style="color:blue;">5. Excepción</mark> <mark style="color:blue;"></mark><mark style="color:blue;">`AssertionError`</mark>

Como complemento a todas las sentencias anteriores, Python nos proporciona una sentencia adicional que nos permite verificar en un punto determinado de nuestro programa que todo esta funcionando adecuadamente, esta sentencia es `assert`.

```python
passwd = input("Introduce una contraseña de más de 8 digitos:")
assert len(passwd) > 8, "[!] La contraseña es menor a 8 digitos"

Introduce una contraseña de más de 8 digitos: 1234
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[22], line 3
      1 passwd = input("Introduce una contraseña de más de 8 digitos:")
----> 3 assert len(passwd) > 8, "[!] La contraseña es menor a 8 digitos"

AssertionError: [!] La contraseña es menor a 8 digitos
```

## <mark style="color:blue;">6. Cláusula</mark> <mark style="color:blue;"></mark><mark style="color:blue;">`else`</mark> <mark style="color:blue;"></mark><mark style="color:blue;">en excepciones</mark>

Curiosamente, Python nos proporciona un mecanismo por el cual utilizando la sentencia `else`, se puede indicar a un programa que ejecute un determinado bloque de código sólo en ausencia de excepciones.

```python
try:
    print(variable_no_definida)
except NameError:
    print("[!] La variable no está definida, se define con la cadena 'Hola mundo'")
    variable_no_definida = 'Hola mundo'
else:
    print("[+] La variable ya estaba definida con el valor:", variable_no_definida)
    
Hola mundo
[+] La variable ya estaba definida con el valor: Hola mundo
```

## <mark style="color:blue;">7. Sentencia</mark> <mark style="color:blue;"></mark><mark style="color:blue;">`finally`</mark>

Python nos proporciona una última sentencia que podemos utilizar para realizar una "limpieza" después de la ejecución de nuestro código al gestionar una excepción. Esta sentencia se denomina `finally` y el código que se localice en su cuerpo, se ejecutará siempre, independientemente de si se produce o no la excepción.

```python
try:
    print(variable3)
except NameError:
    print("[!] La variable no está definida, se define con la cadena 'Hola mundo'")
    variable3 = 'Hola mundo'
else:
    print("[+] La variable ya estaba definida con el valor:", variable3)
    
Hola mundo
[+] La variable ya estaba definida con el valor: Hola mundo
```

```python
try:
    print(variable3)
except NameError:
    print("[!] La variable no está definida, se define con la cadena 'Hola mundo'")
    variable3 = 'Hola mundo'
else:
    print("[+] La variable ya estaba definida con el valor:", variable3)
finally:
    del variable3 # Siempre se ejecutará esta línea de código
    
[!] La variable no está definida, se define con la cadena 'Hola mundo'
```

