Python Intermedio Readthedocs Io Es Latest
Python Intermedio Readthedocs Io Es Latest
Python Intermedio Readthedocs Io Es Latest
Versión 0.1
Traducción: ellibrodepython.com
16 de marzo de 2021
Contents
1 Introducción 2
2 Autor 3
3 Tabla de Contenido 4
3.1 Uso de *args y **kwargs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.2 Depurando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.3 Generadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.4 Map, Filter y Reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.5 Estructura de datos set . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.6 Operadores ternarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.7 Decoradores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.8 Global & Return . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.9 Mutabilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.10 Método mágico __slots__ . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.11 Entornos virtuales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.12 Colecciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.13 Enumerados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.14 Introspección de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.15 Comprensión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.16 Excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.17 Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.18 Funciones Lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.19 Ejemplos en 1 línea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.20 for/else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.21 Extensiones C de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.22 Función open . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.23 Usando Python 2+3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
3.24 Corrutinas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
3.25 Caching de Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
3.26 Gestores de Contexto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
i
Python Intermedio, Versión 0.1
Nota: Este documento contiene una serie de tutoriales y ejemplos en Python para un
nivel intermedio. Ha sido traducido por la comunidad ellibrodepython.com y todo el
contenido ha sido donado bajo licencia de Creative Commons, por lo que siéntete libre
para compartirlo, modificarlo o colaborar. Libro original: intermediatePython
Contents 1
CHAPTER 1
Introducción
2
CHAPTER 2
Autor
Sobre el autor de la traducción: Este libro es una traducción en Español escrita por
cursospython.com titulada Python Intermedio. Somos una comunidad de Python en es-
pañol, comprometida a crear documentación y tutoriales sobre el lenguaje, accesibles
de manera gratuita en nuestra web. Colaboramos con la comunidad open source y esta
traducción ha sido donada al igual que el original bajo una licencia Creative Commons
CC BY-NC-SA 4.0, lo que principalmente dice que puedes hacer lo que quieras con
este libro siempre y cuando menciones la fuente y sea con fines no comerciales.
Sobre el autor original: Su versión original es de Muhammad Yasoob Ullah Khalid, un
fan de Python con varios años de experiencia en el lenguaje, y conocido en la comu-
nidad. Participó en 2014 en EuroPython en Berlin, una de las mayores conferencias de
Python en Europa.
3
CHAPTER 3
Tabla de Contenido
4
Python Intermedio, Versión 0.1
Espero que esto haya aclarado el uso de *args, continuemos con **kwargs.
>>> saludame(nombre="Covadonga")
nombre = Covadonga
Es decir, dentro de la función no solo tenemos acceso a la variable como con *args,
sino que también tenemos acceso a un nombre o key asociado. A continuación vere-
mos como se puede usar *args y **kwargs para llamar a una función con una lista o
diccionario como argumentos.
Ahora veremos como se puede llamar a una función usando *args y **kwargs. Con-
sideremos la siguiente.
def test_args_kwargs(arg1, arg2, arg3):
print("arg1:", arg1)
print("arg2:", arg2)
print("arg3:", arg3)
Ahora puedes usar *args o **kwargs para pasarle argumentos a la función. Se puede
hacer de la siguiente manera:
# Primero con *args
>>> args = ("dos", 3, 5)
>>> test_args_kwargs(*args)
arg1: dos
arg2: 3
arg3: 5
Por último, si quieres usar los tres tipos de argumentos de entrada a una función:
normales, *args y **kwargs, deberás hacerlo en el siguiente orden.
Dependerá mucho de los requisitos de tu programa, pero uno de los usos más co-
munes es para crear decoradores para funciones (que veremos en otro capítulo). Tam-
bién puede ser usado para monkey patching, lo que significa modificar código en tiempo
de ejecución. Considera por ejemplo que tienes una clase con una función llamada
get_info que llama a una API que devuelve una determinada respuesta. Si quieres
testearla, se puede reemplazar la llamada a la API por unos datos de test, como por
ejemplo:
import someclass
someclass.get_info = get_info
3.2 Depurando
Depurar es una de las herramientas que mas nos pueden ayudar si tenemos un bug o
fallo que necesitamos resolver. Mucha gente olvida la importancia del depurador de
Python pdb. En esta sección veremos algunos de los comandos más importantes, por
lo que si quieres entrar en detalle, no olvides entrar en la documentación oficial.
Desde línea de comandos
Puedes ejecutar un script desde la línea de comandos usando el depurador de Python.
Se hace de la siguiente manera:
Esto hará que el depurador pare la ejecución del programa en la primera sentencia
que encuentre. Su uso es muy útil cuando el script es corto. Puedes inspeccionar las
variables y continuar con la ejecución línea por línea.
3.2. Depurando 6
Python Intermedio, Versión 0.1
import pdb
def haz_algo():
pdb.set_trace()
return "No quiero"
print(haz_algo())
3.3 Generadores
3.3. Generadores 7
Python Intermedio, Versión 0.1
• Iterable
• Iterador
• Iteración
Todos ellos están relacionados entre sí. A continuación los explicaremos unos por uno.
3.3.1 Iterable
3.3.2 Iterador
Un iterador es cualquier objeto en Python que tenga definidos los métodos next
(Python 2) o __next__. Sabido esto, vamos a ver ahora que es una iteración.
3.3.3 Iteración
3.3.4 Generadores
Los generadores son en realidad iteradores, pero sólo permiten ser iterados una vez.
Esto se debe a que no almacenan todos los valores en memoria, sino que los van
generando al vuelo. Pueden ser usados de dos formas diferentes, iterándolos con un
bucle for o pasándolos a una función como veremos a continuación.
La mayoría de las veces, los generadores son implementados como funciones. Sin
embargo no devuelven los valores con return sino que lo hacen usando yield. Veamos
un ejemplo sencillo de una función generadora.
def funcion_generadora():
for i in range(10):
yield i
# Salida: 0
(continues on next page)
3.3. Generadores 8
Python Intermedio, Versión 0.1
# Usando generadores
def fibon(n):
a = b = 1
for i in range(n):
yield a
a, b = b, a + b
for x in fibon(1000000):
print(x)
De esta manera no nos tenemos que preocupar si usaremos demasiados recursos. Sin
embargo, implementado de la siguiente forma, podríamos llegar a tener problemas:
def fibon(n):
a = b = 1
resultado = []
for i in range(n):
resultado.append(a)
a, b = b, a + b
return resultado
Si con el ejemplo anterior usáramos como entrada un número muy elevado, po-
dríamos llegar a tener problemas.
Hasta ahora hemos explicado el uso de los generators pero no hemos llegado a pro-
barlos. Antes de probarlos, es necesario saber un poco más acerca de la función next()
de Python. Esta función nos permite acceder al siguiente elemento de una secuencia:
3.3. Generadores 9
Python Intermedio, Versión 0.1
def funcion_generadora():
for i in range(3):
yield i
gen = funcion_generadora()
print(next(gen))
# Salida: 0
print(next(gen))
# Salida: 1
print(next(gen))
# Salida: 2
print(next(gen))
# Salida: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration
Como podemos ver, cuando se llega al final de la función, si se intenta llamar otra vez
al next() tendremos un error StopIteration, ya que no hay más valores. Esto se debe
a que la función no tiene más valores de los que hacer yield, es decir se ha llegado al
final.
Tal vez te preguntes porque no pasa esto cuando usamos un bucle for. La respuesta
es muy sencilla, el bucle for se encarga automáticamente de capturar este error y de
no llamar más a next. ¿Sabías que algunas funciones que vienen por defecto también
soportan ser iteradas? Vamos a verlo:
cadena = "Pelayo"
next(my_string)
# Salida: cadena (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: str object is not an iterator
Tal vez no era eso lo que nos esperábamos. El error dice que str (la cadena) no es un
elemento iterador. Bueno, eso es cierto, ya que se trata de un elemento iterable pero
no es un iterador. Esto significa que soporta ser iterado pero que no puede ser iterado
directamente. Entonces, ¿cómo lo iteramos? Veamos como usar la función iter, que
devuelve un objeto iterador (iterator) de una clase iterable.
Entonces, el tipo numérico entero int no es iterable, pero una cadena si que lo es.
Veamos el ejemplo para el int:
int_var = 1779
iter(int_var)
# Salida: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: 'int' object is not iterable
# Sucede ya que no es iterable
my_string = "Pelayo"
my_iter = iter(my_string)
(continues on next page)
3.3. Generadores 10
Python Intermedio, Versión 0.1
Podemos ver entonces como si llamamos a iter sobre un tipo entero, tendremos un
error, ya que los enteros no son iterables. Sin embargo, si realizamos lo mismo con una
cadena, nos devolverá un iterador sobre el que podemos usar next() para ir accedi-
endo secuencialmente a sus valores hasta llegar al final.
Una vez explicado esto, esperamos que hayas entendido los generators y los con-
ceptos asociados como el iterador o que una clase sea iterable. Los generadores son
sin duda una herramienta muy potente, por lo que te recomendamos que tengas los
ojos abiertos porque seguramente encontrarás alguna aplicación donde te ayuden a
resolver un problema.
3.4.1 Map
El uso de map aplica una determinada función a todos los elementos de una entrada o
lista. Esta es su forma:
Forma
map(funcion_a_aplicar, lista_de_entradas)
Se trata de un caso de uso bastante recurrente. Imaginemos por ejemplo que tenemos
una lista y queremos crear otra lista con todos sus elementos elevados al cuadrado. La
primera forma que tal vez se nos ocurra, sería la siguiente:
lista = [1, 2, 3, 4, 5]
al_cuadrado = []
for i in lista:
al_cuadrado.append(i**2)
Sin embargo, existe una forma más fácil de hacerlo con map. Es mucho más sencilla y
corta:
lista = [1, 2, 3, 4, 5]
al_cuadrado = list(map(lambda x: x**2, lista))
Otra forma de usar map es teniendo una lista de funciones en vez de una en concreto.
Veamos un ejemplo:
def multiplicar(x):
return (x*x)
def sumar(x):
return (x+x)
# Salida:
# [0, 0]
# [1, 2]
# [4, 4]
# [9, 6]
# [16, 8]
Se puede ver como ahora para cada elemento (del 0 al 4) tenemos dos salida, la primera
aplica la función multiplicar y la segunda sumar.
3.4.2 Filter
Como su nombre indica, filter crea una lista de elementos si usados en la llamada
a una función devuelven True. Es decir, filtra los elementos de una lista usando un
determinado criterio. Veamos un ejemplo:
lista = range(-5, 5)
menor_cero = list(filter(lambda x: x < 0, lista))
print(menor_cero)
3.4.3 Reduce
Por último, reduce es muy útil cuando queremos realizar ciertas operaciones sobre
una lista y devolver su resultado. Por ejemplo, si queremos calcular el producto de
todos los elementos de una lista, y devolver un único valor, podríamos hacerlo de la
siguiente forma sin usar reduce.
producto = 1
lista = [1, 2, 3, 4]
for num in lista:
producto = producto * num
# producto = 24
# Salida: 24
El set es una estructura de datos muy usada. Los sets se comportan como las listas,
con la diferencia de que no pueden contener elementos duplicados. También son in-
mutables, y una vez son definidos sus elementos no pueden ser modificados. Tampoco
son ordenadores, por lo que no respetan el orden en el que son definidos. Son útiles
si por ejemplo quieres ver si en una lista hay duplicados o no. Tienes dos opciones de
hacerlo, donde la primera usa un bucle for:
duplicados = []
for value in lista:
if lista.count(value) > 1:
if value not in duplicados:
duplicados.append(value)
print(duplicados)
# Salida: ['b', 'n']
Pero hay una forma más simple y elegante de realizar la misma tarea usando los sets.
Lo vemos a continuación:
Diferencia
Con el método difference podemos calcular la diferencia entre dos sets. Es impor-
tante notar que no es lo mismo la diferencia A-B que B-A. En el siguiente caso se ve
como la diferencia del set2 y el set1 son los elementos del set2 que no están presentes
en el set1.
Existen otros métodos del set muy bien explicados en este post.
Los operadores ternarios son más conocidos en Python como expresiones condi-
cionales. Estos operadores evalúan si una expresión es verdadera o no. Se añadieron
a Python en la versión 2.4.
Forma:
Ejemplo:
es_bonito = True
estado = "Es bonito" if es_bonito else "No es bonito"
Si te quedas con dudas te recomendamos este post donde se explican con más ejemp-
los.
Como se puede ver, permiten verificar de manera rápida una condición, y lo mejor de
todo es que se puede hacer en una sola línea de código. Por lo general hacen que el
código sea más compacto y fácil de leer.
Otra forma un tanto extraña y no demasiado usada es la siguiente:
Forma:
(if_test_is_false, if_test_is_true)[test]
Example:
es_bonito = True
apariencia = ("Feo", "Bonito")[es_bonito]
print("El gato es ", apariencia)
# Salida: El gato es bonito
Este ejemplo funciona ya que True=1 y False=0, y puede ser usado también con listas.
Es importante decir también que este ejemplo no es muy usado, y por lo general no
gusta a los Pythonistas.
Otro de los motivos por los que no resulta del todo correcto su uso, es que ambos
elementos son evaluados, mientras que en operador ternario if-else no.
Ejemplo:
condicion = True
print(2 if condition else 1/0)
#Salida is 2
print((1/0, 2)[condicion])
#Se lanza ZeroDivisionError
O también es una forma muy simple de definir parámetros con valores por defecto
dinámicos. En el siguiente ejemplo vemos como se imprime el nombre_real por de-
fecto, pero si se proporciona también un nombre_opcional se imprimirá este por pan-
talla en vez del anterior.
3.7 Decoradores
Antes de entrar en materia con los decoradores, vamos a entender bien las funciones.
def hola(nombre="Covadonga"):
return "Hola " + nombre
print(hola())
# Salida: 'Hola Covadonga'
print(saluda())
# Salida: 'Hola Covadonga'
3.7. Decoradores 16
Python Intermedio, Versión 0.1
print(saluda())
#Salida: 'Hola Covadonga'
Vamos a ir un paso más allá. En Python podemos definir funciones dentro de otras
funciones. Veamos un ejemplo:
def hola(nombre="Covadonga"):
print("Estás dentro de la función hola()")
def saluda():
return "Estás dentro de la función saluda()"
def bienvenida():
return "Estás dentro de la función bienvenida()"
print(saluda())
print(bienvenida())
print("De vuelta a la función hola()")
hi()
#Salida:Estas dentro de la función hola()
# Estás dentro de la función saluda()
# Estás dentro de la función bienvenida()
# De vuelta a la función hola()
saluda()
#Saluda: NameError: name 'saluda' is not defined
Ya hemos visto entonces como podemos definir funciones dentro de otras funciones.
En otras palabras, podemos crear funciones anidadas. Pero para entender bien los
decoradores, necesitamos ir un paso más allá. Las funciones también pueden devolver
otras funciones.
3.7. Decoradores 17
Python Intermedio, Versión 0.1
def hola(nombre="Covadonga"):
def saluda():
return "Estás dentro de la función saluda()"
def bienvenida():
return "Estás dentro de la función bienvenida()"
if nombre == "Covadonga":
return saluda
else:
return bienvenida
a = hola()
print(a)
#Salida: <function saluda at 0x7f2143c01500>
print(a())
#Salida: Estás dentro de la función saluda()
Echa un vistazo otra vez al código. Si te fijas en el if/else, estamos devolviendo saluda
y bienvenida y no saluda() y bienvenida(). ¿A qué se debe esto? Se debe a que
cuando usas paréntesis () la función se ejecuta. Por lo contrario, si no los usas la
función es pasada y puede ser asignada a una variable sin ser ejecutada.
Vamos a analizar el código paso por paso. Al principio usamos a = hola(), por lo que
el parámetro para nombre que se toma es Covadonga ya que es el que hemos asignado
por defecto. Esto hará que en el if se entre en nombre == "Covadonga", lo que hará que
se devuelva la función saluda. Si por lo contrario hacemos la llamada a la función con
a = hola(nombre="Pelayo"), la función devuelta será bienvenida.
Por último, podemos hacer que una función tenga a otra como entrada y que
además la ejecute dentro de sí misma. En el siguiente ejemplo podemos ver como
hazEstoAntesDeHola() es una función que de alguna forma encapsula a la función
que se le pase como parámetro, añadiendo una determinada funcionalidad. En este
ejemplo simplemente imprimimos algo por pantalla antes de llamar a la función.
def hola():
return "¡Hola!"
def hazEstoAntesDeHola(func):
print("Hacer algo antes de llamar a func")
print(func())
3.7. Decoradores 18
Python Intermedio, Versión 0.1
Ahora ya tienes todas las piezas del rompecabezas. Los decoradores son funciones que
decoran a otras funciones, pudiendo ejecutar código antes y después de la función que
está siendo decorada.
def envuelveLaFuncion():
print("Haciendo algo antes de llamar a a_func()")
a_func()
return envuelveLaFuncion
def funcion_a_decorar():
print("Soy la función que necesita ser decorada")
funcion_a_decorar()
#Salida: "Soy la función que necesita ser decorada"
funcion_a_decorar = nuevo_decorador(funcion_a_decorar)
#Ahora funcion_a_decorar está envuelta con el decorador que hemos creado
funcion_a_decorar()
#Salida: Haciendo algo antes de llamar a a_func()
# Soy la función que necesita ser decorada
# Haciendo algo después de llamar a a_func()
3.7. Decoradores 19
Python Intermedio, Versión 0.1
funcion_a_decorar()
#Salida: Haciendo algo antes de llamar a a_func()
# Soy la función que necesita ser decorada
# Haciendo algo después de llamar a a_func()
Una vez visto esto, hay un pequeño problema con el código. Si ejecutamos lo siguiente:
print(funcion_a_decorar.__name__)
# Output: envuelveLaFuncion
def nuevo_decorador(a_func):
@wraps(a_func)
def envuelveLaFuncion():
print("Haciendo algo antes de llamar a a_func()")
a_func()
print("Haciendo algo después de llamar a a_func()")
return envuelveLaFuncion
@nuevo_decorador
def funcion_a_decorar():
print("Soy la función que necesita ser decorada")
print(funcion_a_decorar.__name__)
# Salida: funcion_a_decorar
Mucho mejor ahora. Veamos también unos fragmentos de código muy usados.
Ejemplos:
3.7. Decoradores 20
Python Intermedio, Versión 0.1
@nombre_decorador
def func():
return("La función se esta ejecutando")
can_run = True
print(func())
# Salida: La función se esta ejecutando
can_run = False
print(func())
# Salida: La función no se ejecutará
Nota: @wraps toma una función para ser decorada y añade la funcionalidad de copiar
el nombre de la función, el docstring, los argumentos y otros parámetros asociados.
Esto nos permite acceder a los elementos de la función a decorar una vez decorada. Es
decir, resuelve el problema que vimos con anterioridad.
Casos de uso:
A continuación veremos algunos áreas en las que los decoradores son realmente útiles.
Autorización
Los decoradores permiten verificar si alguien está o no autorizado a usar una deter-
minada función, por ejemplo en una aplicación web. Son muy usados en frameworks
como Flask o Django. Aquí te mostramos como usar un decorador para verificar que
se está autenticado.
Ejemplo :
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return f(*args, **kwargs)
return decorated
3.7. Decoradores 21
Python Intermedio, Versión 0.1
Iniciar sesión
El inicio de sesión es otra de las áreas donde los decoradores son muy útiles. Vamos
un ejemplo:
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
"""Función suma"""
return x + x
result = addition_func(4)
# Salida: addition_func was called
Hemos visto ya el uso de @wraps, y tal vez te preguntes ¿pero no es también un deco-
rador? De hecho si te fijas acepta un parámetro (que en nuestro caso es una función). A
continuación te explicamos como crear un decorador que también acepta parámetros
de entrada.
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " fue llamada"
print(log_string)
# Abre el fichero y añade su contenido
with open(logfile, 'a') as opened_file:
# Escribimos en el fichero el contenido
(continues on next page)
3.7. Decoradores 22
Python Intermedio, Versión 0.1
@logit()
def myfunc1():
pass
myfunc1()
# Salida: myfunc1 fue llamada
# Se ha creado un fichero con el nombre por defecto (out.log)
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
# Salida: myfunc2 fue llamada
# Se crea un fichero func2.log
Clases Decoradoras
_logfile = 'out.log'
3.7. Decoradores 23
Python Intermedio, Versión 0.1
def notify(self):
# Esta clase simplemente escribe el log, nada más.
pass
Esta implementación es mucho más limpia que con la función anidada. Por otro lado,
la función puede ser envuelta de la misma forma que veníamos usando hasta ahora,
usando @.
myfunc1()
# Output: myfunc1 fue llamada
Ahora, vamos a crear una subclase de logit para añadir la funcionalidad de enviar un
email. Enviaremos el email de manera ficticia.
class email_logit(logit):
'''
Implementación de logit con envío de email
'''
def __init__(self, email='[email protected]', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
def notify(self):
# Enviamos email a self.email
# Código para enviar email
# ...
pass
Una vez creada la nueva clase que hereda de logit, si usamos @email_logit como
decorador tendrá el mismo comportamiento, pero además enviará un email.
Si quieres saber más acerca de los decoradores, en este post tienes más información.
3.7. Decoradores 24
Python Intermedio, Versión 0.1
Estoy seguro de que por poco código en Python que hayas visto, te habrás encontrado
la sentencia return al final de una función alguna vez. Al igual que en muchos lengua-
jes de programación, nos permite devolver valores a quien llama a la función. Veamos
un ejemplo:
resultado = suma(3, 5)
print(resultado)
# Salida: 8
La función anterior toma dos argumentos de entrada y como salida devuelve su suma.
Otra forma de conseguir el mismo resultado podría haber sido:
suma(3,5)
print(resultado)
# Salida: 8
suma(2, 4)
print(resultado)
suma(2, 4)
print(resultado)
# Salida
# 6
Y como hemos explicado la segunda forma se ejecutará sin problemas. Sin embargo
ten cuidado con el uso de global, ya que suele ser una buena práctica evitar su uso.
No es muy recomendable tener variables globales salvo casos muy excepcionales.
Tal vez quieras devolver más de una variable desde una función. Una primera forma
de hacerlo sería la siguiente, pero de verdad, no te recomendamos que lo hagas así.
Huye de esto y no mires atrás:
def perfil():
global nombre
global edad
name = "Pelayo"
age = 30
perfil()
print(nombre)
# Salida: Pelayo
print(edad)
# Salida: 30
Nota: No hagas esto. Tal vez te preguntes porqué mostramos código que no está bien.
Pues bien, nos gusta mostrar también ejemplos de lo que está mal, ya que ayudan a
entender lo que no se debe hacer.
Otra forma mucho mejor de hacer esto, es devolviendo los datos dentro de una estruc-
tura tipo tuple, list o dict. Una forma de hacerlo sería la siguiente:
def perfil():
nombre = "Pelayo"
(continues on next page)
datos_perfil = perfil()
print(datos_perfil[0])
# Salida: Pelayo
print(datos_perfil[1])
# Salida: 30
Y otra forma prácticamente igual pero más usada por convención sería la siguiente.
def perfil():
nombre = "Pelayo"
edad = 30
return nombre, edad
Ten en cuenta que en el ejemplo anterior también se está devolviendo una tupla
(aunque no haya paréntesis). Vistas estas formas, se podría decir que hay otra forma
un poco más completa que tal vez te sea útil. Se trata del uso de namedtuple. Veamos
un ejemplo:
from collections import namedtuple
def perfil():
Persona = namedtuple('Persona', 'nombre edad')
return Persona(nombre="Pelayo", edad=31)
# Usando el namedtuple
p = perfil()
print(p, type(p))
# Persona(nombre='Pelayo', edad=31) <class '__main__.Persona'>
print(p.nombre)
# Pelayo
print(p.edad)
#31
Esta forma es bastante útil sobre todo debido a que podemos acceder a los elementos
de forma muy sencilla usando . y el argumento. Como hemos mencionado, otra forma
de hacerlo sería con lists y dicts, pero como ya hemos comentado, intenta evitar
global en la medida de lo posible.
3.9 Mutabilidad
Los tipos mutables e inmutables en Python son conceptos que causan verdaderos que-
braderos de cabeza a los programadores. En otras palabras, mutable significa «que
puede cambiar» e inmutable significa «que es constante». ¿Quieres ver lo enrevesado
que puede parecer si no se entiende correctamente? Veamos un ejemplo:
foo = ['hola']
print(foo)
# Salida: ['hola']
bar = foo
bar += ['adios']
print(foo)
# Output: ['hola', 'adios']
¿Que ha ocurrido? Hemos modificado la variable bar, pero la variable foo también ha
sido modificada. Tal vez te esperabas algo como lo que mostramos a continuación:
foo = ['hola']
print(foo)
# Salida: ['hola']
bar = foo
bar += ['adios']
print(foo)
# Salida esperada: ['hola']
# Salida: ['hola', 'adios']
print(bar)
# Salida: ['hola', 'adios']
3.9. Mutabilidad 28
Python Intermedio, Versión 0.1
agrega(1)
# Salida: [1]
agrega(2)
# Salida: [1, 2]
agrega(3)
# Salida: [1, 2, 3]
Tal vez te esperabas otro comportamiento, ya que en cada llamada a agrega estamos
creando una lista nueva vacía. Sería razonable esperar que la salida fuera la siguiente:
agrega(1)
# Salida: [1]
agrega(2)
# Salida: [2]
agrega(3)
# Salida: [3]
Otra vez, estamos viendo la mutabilidad en acción. En Python, los argumentos por
defecto se evalúan una vez que la función ha sido definida, no cada vez que la función
es llamada. Por lo tanto, nunca deberías definir un argumento por defecto de un tipo
mutable, a menos que realmente estés seguro de lo que estas haciendo. El siguiente
ejemplo sería más correcto:
Ahora cada vez que llamamos a la función sin el argumento target, una nueva lista
será creada. Por ejemplo:
3.9. Mutabilidad 29
Python Intermedio, Versión 0.1
agrega(42)
# Salida: [42]
agrega(42)
# Salida: [42]
agrega(42)
# Salida: [42]
En Python cualquier clase tiene atributos de instancia. Por defecto se usa un dic-
cionario para almacenar los atributos de un determinado objeto, y esto es algo muy
útil que permite por ejemplo crear nuevos atributos en tiempo de ejecución.
Sin embargo, para clases pequeñas con atributos conocidos, puede llegar a resultar
un cuello de botella. El uso del diccionario dict desperdicia un montón de memoria
RAM y Python no puede asignar una cantidad de memoria estática para almacenar
los atributos. Por lo tanto, se come un montón de RAM si creas muchos objetos (del
orden de miles o millones). Por suerte hay una forma de solucionar esto, haciendo
uso de __slots__, que permite decirle a Python que no use un diccionario y que solo
asigne memoria para una cantidad fija de atributos. Aquí mostramos un ejemplo del
uso de __slots__:
Sin usar __slots__:
class MiClase(object):
def __init__(self, nombre, identificador):
self.nombre = nombre
self.identificador = identificador
self.iniciar()
# ...
Usando __slots__:
class MiClase(object):
__slots__ = ['nombre', 'identificador']
def __init__(self, nombre, identificador):
self.nombre = nombre
self.identificador = identificador
self.iniciar()
# ...
In [2]: imu.start_watching_memory()
In [2] used 0.0000 MiB RAM in 5.31s, peaked 0.00 MiB above current, total RAM␣
,→usage 15.57 MiB
num = 1024*256
x = [MyClass(1,1) for i in range(num)]
In [3] used 0.2305 MiB RAM in 0.12s, peaked 0.00 MiB above current, total RAM␣
,→usage 15.80 MiB
num = 1024*256
x = [MyClass(1,1) for i in range(num)]
In [5] used 0.1758 MiB RAM in 0.12s, peaked 0.00 MiB above current, total RAM␣
,→usage 25.28 MiB
Se puede ver una clara reducción en el uso de RAM 9.3008 MiB vs 22.6680 MiB.
Los entornos virtuales o virtual environments son una herramienta muy potente que es
parte de cualquier desarrollador de Python. Entonces, ¿qué son los virtualenv?
Se trata de una herramienta que permite crear entornos virtuales de Python totalmente
aislados. Imagina que tienes una aplicación que requiere la versión 2 de Python, pero
que también tienes otra que requiere Python 3. ¿Cómo puedes usar ambas aplica-
ciones? O también puedes tener diferentes aplicaciones que usan diferentes versiones
de un determinado paquete. ¿Cómo podemos hacer? La respuesta son los virtualenv.
Si instalas todo en /usr/lib/python2.7/site-packages (o el directorio que tengas si
usas Windows o cualquier otra plataforma) cualquiera de tus aplicaciones compartirá
por defecto el contenido de esa carpeta, como por ejemplo las versiones de los paque-
tes que usas. El problema es que es normal acabar actualizando algún paquete, y esto
puede ser algo que nos interese para una aplicación que tengamos, pero no para otra.
La solución a este problema es usar virtualenv para crear entornos completamente
aislados unos de otros. Por lo tanto, tus aplicaciones usarán uno determinado, y los
nuevos paquetes o actualizaciones que instales en uno no afectaran a otros.
Para instalar esta herramienta, basta con ejecutar el siguiente comando en el terminal:
$ deactivate
varios proyectos con diferentes entornos y quieres navegar por ellos, ya que el entorno
se irá activando o desactivando según el directorio en el que estés. Puedes leer más
acerca de este proyecto en su GitHub.
Y hasta aquí esta rápida introducción de los virtualenv. Hay mucho más que esto,
por lo que si quieres saber más, te recomendamos el siguiente enlace.
3.12 Colecciones
Python viene con un modulo que contiene varios contenedores de datos llamados
colecciones o collections en Inglés. Hablaremos de algunos de ellos y de sus usos.
En concreto, hablaremos de los siguientes:
• defaultdict
• OrderedDict
• counter
• deque
• namedtuple
• enum.Enum (fuera del módulo; Python 3.4+)
3.12.1 defaultdict
colours = (
('Asturias', 'Oviedo'),
('Galicia', 'Ourense'),
('Extremadura', 'Cáceres'),
('Galicia', 'Pontevedra'),
('Asturias', 'Gijón'),
('Cataluña', 'Barcelona'),
)
ciudades = defaultdict(list)
print(ciudades)
3.12. Colecciones 33
Python Intermedio, Versión 0.1
Una de las ocasiones en las que son más útiles, es si quieres añadir elementos a listas
anidadas dentro e un diccionario. Si la llave o key no está ya presente en el diccionario,
tendrás un error tipo KeyError. El uso de defaultdict permite evitar este problema.
Antes de nada, vamos a ver un ejemplo con dict que daría un error KeyError como
hemos mencionado, y después veremos la solución usando defaultdict.
Problema:
some_dict = {}
some_dict['region']['ciudad'] = "Oviedo"
# Raises KeyError: 'region'
Solución:
import json
print(json.dumps(some_dict))
# Output: {"region": {"ciudad": "Oviedo"}}
3.12.2 OrderedDict
OrderedDict es un diccionario que mantiene ordenadas sus entradas según van siendo
añadidas. Es importante saber también que sobreescribir un valor existente no cambia
la posición de la llave o key. Sin embargo, eliminar y reinsertar una entrar mueve la
llave al final del diccionario.
Problema:
3.12. Colecciones 34
Python Intermedio, Versión 0.1
Solución:
3.12.3 counter
El uso de counter nos permite contar el número de elementos que una llave tiene.
Por ejemplo, puede ser usado para contar el número de colores favoritos de diferentes
personas.
colours = (
('Covadonga', 'Amarillo'),
('Pelayo', 'Azul'),
('Xavier', 'Verde'),
('Pelayo', 'Negro'),
('Covadonga', 'Rojo'),
('Amaya', 'Plata'),
)
También podemos contar las líneas más comunes de un fichero, como por ejemplo:
3.12. Colecciones 35
Python Intermedio, Versión 0.1
3.12.4 deque
deque proporciona una cola con dos lados, lo que significa que puedes añadir y elimi-
nar elementos de cualquiera de los lados de la cola. Primero debes importar el módulo
de la librería de colecciones o collections:
d = deque()
d = deque()
d.append('1')
d.append('2')
d.append('3')
print(len(d))
# Salida: 3
print(d[0])
# Salida: '1'
print(d[-1])
# Salida: '3'
También puedes tomar elementos de los dos lados de la cola, una funcionalidad cono-
cida como pop. Es importante notar que pop devuelve el elemento eliminado.
d = deque(range(5))
print(len(d))
# Salida: 5
d.popleft()
# Salida: 0
d.pop()
# Salida: 4
print(d)
# Salida: deque([1, 2, 3])
3.12. Colecciones 36
Python Intermedio, Versión 0.1
También podemos limitar la cantidad de elementos que la cola deque puede almacenar.
Al hacer esto, simplemente quitará elementos del otro lado de la cola si el límite es
superado. Se ve mejor con un ejemplo como se muestra a continuación:
d.extend([6])
print(d)
#Salida: deque([1, 2, 3, 5, 6], maxlen=5)
Ahora cuando insertamos valores después del 5, la parte más a la izquierda será elim-
inada de la lista. También puedes expandir la lista en cualquier dirección con valores
nuevos.
d = deque([1,2,3,4,5])
d.extendleft([0])
d.extend([6,7,8])
print(d)
# Salida: deque([0, 1, 2, 3, 4, 5, 6, 7, 8])
3.12.5 namedtuple
Tal vez conozcas ya las tupas, que son listas inmutables que permiten almacenar una
secuencia de valores separados por coma. Son simplemente como las listas pero con al-
gunas diferencias importantes. La principal es que a diferencia de las listas no puedes
reasignar el valor de un elemento una vez inicializada. Para acceder a un índice de la
tupla se hace de la siguiente manera:
Sabido esto, ¿qué son las namedtuples?. Se trata de un tipo que convierte las tuplas en
contenedores bastante útiles para tareas simples. Con ellas, no necesitas usar índices
enteros para acceder a los miembros de la misma. Puedes pensar en ellas como si
fuesen diccionarios, con la salvedad de que son inmutables. Veamos un ejemplo.
print(perry)
# Salida: Animal(nombre='perry', edad=31, tipo='cat')
print(perry.nombre)
# Salida: 'perry'
3.12. Colecciones 37
Python Intermedio, Versión 0.1
Puedes ver como es posible acceder a los elementos a través de su nombre, simple-
mente haciendo uso de .. Vamos a verlo con más detalle. Una namedtuple requiere
de dos argumentos. Estos son, el nombre de la tupla y los campos de la misma. En
el ejemplo anterior hemos visto como el nombre de la tupla era “Animal” y tenía tres
atributos: “nombre”, “edad” y “tipo”.
Las namedtuple son muy útiles ya que hacen que las tuplas tengan una especie de
documentación propia, y apenas sea necesaria una explicación de como usarlas, ya
que puedes verlo con un simple vistazo al código. Además, dado que no es necesario
usar índices, hace que sea más fácil de mantener.
Otra de las ventajas es que son bastante ligeras, y no necesitan mas memoria que las
tuplas normales. Esto hace que sean mas rápidas que los diccionarios. Sin embargo,
recuerda que los atributos de las tuplas son inmutables, por lo que no pueden ser
modificados. El siguiente ejemplo no funcionaría:
Deberías usar las namedtuple si quieres que tu código sea autodocumentado. Lo mejor
de todo es que ofrecen compatibilidad con las tuplas, por lo que puedes indexarlas
como si de una tupla normal se tratase. Veamos un ejemplo:
Por último, aunque no por ello menos importante, puedes convertir una namedtuple
en un diccionario. Se puede hacer de la siguiente manera:
3.12. Colecciones 38
Python Intermedio, Versión 0.1
Otra de las colecciones más útiles de Python es el tipo enum, que se encuentra
disponible en el módulo enum desde Python 3.4 en adelante (también está disponible
como backport en PyPI bajo el nombre enum32). Los enums (enumerated type) son bási-
camente una forma de organizar aquellos nombres que puedan tomar un determinado
número de estados limitados y claramente definidos.
Vamos a considerar el ejemplo anterior en namedtuples del Animal. Si recuerdas,
había un campo denominado tipo. El problema de este tipo es que era una cadena.
¿Qué pasaría si escribimos Gato o GATO?
El uso de enum nos puede ayudar a resolver este problema, evitando por lo tanto usar
cadenas. Veamos el siguiente ejemplo:
class Especies(Enum):
gato = 1
perro = 2
caballo = 3
lobo = 4
mariposa = 5
buho = 6
# ¡Y muchos más!
# Y un ejemplo
>>> perry.type == luna.type
True
>>> luna.type
<Especies.gato: 1>
Un código así es mucho menos propenso a tener fallos. Si necesitamos ser específicos,
deberíamos usar sólo los tipos enumerados.
Por último, existen tres formas de acceder a los enum. Sigamos con el ejemplo anterior
de las especies. Vamos a acceder a gato:
Especies(1)
Especies['cat']
(continues on next page)
3.12. Colecciones 39
Python Intermedio, Versión 0.1
3.13 Enumerados
Python viene con un tipo por defecto denominado Enumerate. Permite asignar índices
a elementos de, por ejemplo una lista. Veamos un ejemplo:
También acepta un parametro opcional que que lo hace aún más útil.
# Salida:
# 1 Ibias
# 2 Pesoz
# 3 Tinero
# 4 Boal
Este argumento opcional nos permite decirle al enumerate el primer elemento del
índice. También puedes creas tuplas que contengan el índice y la lista. Por ejemplo:
3.13. Enumerados 40
Python Intermedio, Versión 0.1
3.14.1 dir
mi_lista = [1, 2, 3]
dir(mi_lista)
# Salida: ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
# '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
# '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__',
# '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__',
# '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__
,→',
Como podemos ver, se nos devuelven todos los atributos y métodos en una lista. Esto
puede ser útil si no recuerdas el nombre de un método y no tienes la documentación a
mano. Si ejecutamos dir() sin ningún argumento, se nos devolverá todos los nombres
en el scope actual.
3.14.2 type y id
print(type(''))
# Salida: <type 'str'>
print(type([]))
# Salida: <type 'list'>
print(type({}))
# Salida: <type 'dict'>
print(type(dict))
# Salida: <type 'type'>
print(type(3))
# Salida: <type 'int'>
nombre = "Pelayo"
print(id(nombre))
# Salida: 139972439030304
import inspect
print(inspect.getmembers(str))
# Salida: [('__add__', <slot wrapper '__add__' of ... ...
Existen también otros métodos para realizar introspección sobre objetos. Te recomen-
damos que consultes la documentación oficial y leas sobre ellos.
3.15 Comprensión
La comprensión o comprehensions en Python son una de las características que una vez
sabes usarlas, echarias mucho de menos si las quitaran. Se trata de un tipo de con-
strucción que permite crear secuencias a partir de otras secuencias. Existen diferentes
comprehensions soportadas en Python 2 y Python 3:
• Comprensión de listas
• Comprensión de diccionarios
• Comprensión de sets
• Comprensión de generadores
A continuación las explicaremos una por una. Una vez que entiendes el uso con las
listas, cualquiera de las otras será entendia my fácilmente.
Las comprensiones de listas nos proporcionan una forma corta y concisa de crear listas.
Se usan con corchetes [] y en su interior contienen una expresión seguida de un bucle
for y cero o más sentencias for o if. La expresión puede ser cualquier cosa que se
te ocurra, lo que significa que puedes usar cualquier tipo de objetos en la lista. El
resultado es una nueva lista creada tras evaluar las expresiones que haya dentro.
Uso
3.15. Comprensión 42
Python Intermedio, Versión 0.1
Esto puede ser realmente útil para crear listas de manera rápida. De hecho hay gente
que las prefiere sobre el uso de la función filter. Las comprensiones de listas son la
mejor opción si por ejemplo quieres añadir elementos a una lista fruto de un bucle for.
Si queremos hacer algo como lo siguiente:
squared = []
for x in range(10):
squared.append(x**2)
Se podría simplificar en una línea de código con el uso de las comprensiones de listas:
Los diccionarios se usan de una manera muy similar. Aquí vemos un ejemplo:
mcase_frequency = {
k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0)
for k in mcase.keys()
}
En el ejemplo anterior combinamos los valores de las llaves o keys del diccionario que
sean las mismas pero en mayúsculas o minúsculas. Es decir, el contenido de “a” y “A”
se juntaría.
Otro ejemplo podría ser invertir las llaves y valores de un diccionario como se muestra
a continuación:
Las comprensiones en los sets son muy similares a las listas. La única diferencia es que
es necesario hacer uso de llaves {} en vez de corchetes.
3.15. Comprensión 43
Python Intermedio, Versión 0.1
Por último, tenemos los generadores. La única diferencia es que no asignan memo-
ria para toda la lista, sino que la asignan elemento a elemento, lo que las hace mas
eficientes desde el punto de vista del uso de la memoria.
3.16 Excepciones
El manejo de excepciones es un arte que una vez que entiendes resulta de lo mas útil.
Vamos a ver como pueden ser manejadas en Python.
Nota: Si aún no sabes muy bien lo que son las excepciones, te recomendamos empezar
por este post y este otro, donde se explican de manera muy sencilla y didáctica.
Empecemos con el uso de try/except. El código que puede causar una excepción se
pone en el try y el código que maneja esa excepción se ubica en el bloque except.
Veamos un ejemplo:
try:
file = open('test.txt', 'rb')
except IOError as e:
print('Ocurrió un IOError {}'.format(e.args[-1]))
En ejemplo anterior estamos manejando la excepción IOError. Otra cosa que veremos
a continuación es que en realidad podemos manejar varias excepciones.
try:
file = open('test.txt', 'rb')
except (IOError, EOFError) as e:
print("Ocurrió un error. {}".format(e.args[-1]))
3.16. Excepciones 44
Python Intermedio, Versión 0.1
try:
file = open('test.txt', 'rb')
except EOFError as e:
print("Ocurrió un EOFError")
except IOError as e:
print("Ocurrió un IOError")
try:
file = open('test.txt', 'rb')
except Exception as e:
# Puedes añadir algún tipo de información extra
pass
Esto puede ser útil cuando no se sabe con certeza que excepciones pueden ser lanzadas
por el programa.
Uso de finally
Ya hemos visto que debemos ubicar el código que pueda causar una excepción en el
try, y que en el except podemos tratar lo que hacer en el caso de que se produzca
una excepción determinada. A continuación veremos el uso del finally, que permite
ejecutar un determinado bloque de código siempre, se haya producido o no una ex-
cepción. Se trata de un bloque muy importante, y que suele ser usado para ejecutar
alguna tarea de limpieza. Veamos un ejemplo:
try:
file = open('test.txt', 'rb')
except IOError as e:
print('Ocurrió un IOError. {}'.format(e.args[-1]))
finally:
print("Se entra aquí siempre, haya o no haya excepción")
Uso de try/else
Puede ser también útil tener una determinada sección de código que sea ejecutada si
no se ha producido ninguna excepción. Esto se puede realizar con el uso de else. Se
trata de algo bastante útil porque puede haber determinadas secciones de código que
sólo tengan sentido ejecutar si el bloque completo try se ha ejecutado correctamente.
3.16. Excepciones 45
Python Intermedio, Versión 0.1
Si bien es cierto que no es muy habitual ver su uso, es una herramienta a tener en
cuenta.
try:
print('Estoy seguro de que no ocurrirá ninguna excepción')
except Exception:
print('Excepción')
else:
# El código de esta sección se ejecutará si no se produce
# ninguna excepción. Las excepciones producidas aquí
# tampoco serán capturadas.
print('Esto se ejecuta si no ocurre ninguna excepción')
finally:
print('Esto se imprimirá siempre')
3.17 Clases
Las clases son el núcleo de Python. Nos dan un montón de poder, pero es muy fácil
usarlo de manera incorrecta. En esta sección compartiremos algunos de los trucos
relacionados con las clases en Python. ¡Vamos a por ello!
Nota: Si aún no entiendes bien la Programación Orientada a Objetos, te recomen-
damos que empieces antes por este post dónde se explica de manera muy fácil la POO
y conceptos relacionados como la herencia y los métodos estáticos y de clase.
3.17. Clases 46
Python Intermedio, Versión 0.1
class Cal(object):
# pi es una variable de clase
pi = 3.142
def area(self):
return self.pi * (self.radio ** 2)
a = Cal(32)
a.area()
# Salida: 3217.408
a.pi
# Salida: 3.142
a.pi = 43
a.pi
# Salida: 43
b = Cal(44)
b.area()
# Salida: 6082.912
b.pi
# Salida: 3.142
b.pi = 50
b.pi
# Salida: 50
class SuperClass(object):
superpowers = []
foo = SuperClass('foo')
bar = SuperClass('bar')
foo.name
# Salida: 'foo'
3.17. Clases 47
Python Intermedio, Versión 0.1
foo.add_superpower('fly')
bar.superpowers
# Salida: ['fly']
foo.superpowers
# Salida: ['fly']
Esto es un mal uso de las variables de clase. Si te das cuenta la llamada add_superpower
sobre el objeto foo modifica la variable de clase superpowers, y dado que es compar-
tida por todos los objetos de la clase, hace que bar también cambie. Por lo tanto es
importante tener cuidado con esto, y salvo que realmente sepas lo que estás haciendo,
no es muy recomendable usar variables de clase mutables.
Un nuevo estilo de clases fue introducido en Python 2.1, pero mucha gente aún no sabe
de ello. Puede ser en parte porque Python sigue manteniendo el antiguo estilo para
mantener lo que se llama compatibilidad hacia atrás o backward compatibility. Veamos
las diferencias:
• En el estilo antiguo, las clases no heredan de nada.
• En el nuevo estilo las clases heredan de object.
Un ejemplo muy sencillo podría ser:
class OldClass():
def __init__(self):
print('I am an old class')
class NewClass(object):
def __init__(self):
print('I am a jazzy new class')
old = OldClass()
# Salida: I am an old class
new = NewClass()
# Salida: I am a jazzy new class
Esta herencia de object permite que las clases pueden utilizar cierta magia. Una de
las principales ventajas es que puedes hacer uso de diferentes optimizaciones como
__slots__. También puedes hacer uso de super() o de descriptores. ¿Conclusión?
Intenta usar el nuevo estilo de clases.
Nota: Python 3 solo tiene el estilo nuevo de clases. No importa si heredas de object o
3.17. Clases 48
Python Intermedio, Versión 0.1
no. Sin embargo es recomendable que heredes de object, aunque tal vez en la práctica
tampoco se hace.
Las clases en Python son famosas por sus métodos mágicos, comúnmente referidos
con dunder que viene del Inglés y significa double underscore. Es decir, son métodos
definidos con doble barra baja, tanto al principio con al final del nombre del mismo.
Vamos a explicar algunos de ellos.
• __init__
Se trata de un inicializador de clase o también conocido como constructor. Cuando
una instancia de una clase es creada, el método __init__ es llamado. Por ejemplo:
class GetTest(object):
def __init__(self):
print('Saludos!!')
def another_method(self):
print('Soy otro método que no es llamado'
' automáticamente')
a = GetTest()
# Salida: Saludos!!
a.another_method()
# Salida: Soy otro método que no es llamado automáticamente
# called
class GetTest(object):
def __init__(self, name):
print('Saludos!! {0}'.format(name))
def another_method(self):
print('Soy otro método que no es llamado'
' automáticamente')
a = GetTest('Pelayo')
# Salida: Saludos!! Pelayo
3.17. Clases 49
Python Intermedio, Versión 0.1
• __getitem__
Implementar el método __getitem__ en una clase permite a la instancia usar [] para
indexar sus elementos. Veamos un ejemplo:
class GetTest(object):
def __init__(self):
self.info = {
'name':'Covadonga',
'country':'Asturias',
'number':12345812
}
def __getitem__(self,i):
return self.info[i]
foo = GetTest()
foo['name']
# Output: 'Covadonga'
foo['number']
# Output: 12345812
>>> foo['name']
Las funciones lambda son funciones que se definen en una línea, y son conocidas en
otros lenguajes como funciones anónimas. Uno de sus usos es cuando tienes una deter-
minada función que sólo vas a llamar una vez. Por lo demás, su uso y comportamiento
es muy similar a las funciones «normales».
Forma
Ejemplo
suma = lambda x, y: x + y
print(suma(3, 5))
# Salida: 8
print(a)
# Salida: [(13, -3), (4, 1), (1, 2), (9, 10)]
Si quieres saber más acerca de las funciones lambda, puedes encontrar más informa-
ción en este post post.
En este capítulo veremos algunos ejemplos en Python que pueden ser escritos en una
sola línea de código.
Servidor Web
¿Alguna vez has querido enviar un fichero a través de la red? En Python se puede
hacer de manera muy fácil de la siguiente forma. Vete al directorio donde tengas el
fichero, y escribe el siguiente código.
# Python 2
python -m SimpleHTTPServer
# Python 3
python -m http.server
Prints Organizados
Algo muy común a lo que a veces nos enfrentamos, es tener que imprimir un de-
terminado tipo con print(), pero a veces nos encontramos con un contenido que es
prácticamente imposible de leer. Supongamos que tenemos un diccionario. A contin-
uación mostramos como imprimirlo de una manera más organizada. Para ello usamos
pprint() que viene de pretty (bonito).
from pprint import pprint
,→'sort']
Python Intermedio, Versión 0.1
pprint(dir(my_dict))
# ['__add__',
# '__class__',
# '__contains__',
# '__delattr__',
# '__delitem__',
# '__dir__',
# '__doc__',
# '__eq__',
# '__format__',
# '__ge__',
# '__getattribute__',
# '__getitem__',
# '__gt__',
# '__hash__',
# '__iadd__',
# '__imul__',
# '__init__',
# '__init_subclass__',
# '__iter__',
# '__le__',
# '__len__',
# '__lt__',
# '__mul__',
# '__ne__',
# '__new__',
# '__reduce__',
# '__reduce_ex__',
# '__repr__',
# '__reversed__',
# '__rmul__',
# '__setattr__',
# '__setitem__',
# '__sizeof__',
# '__str__',
# '__subclasshook__',
# 'append',
# 'clear',
# 'copy',
# 'count',
# 'extend',
# 'index',
# 'insert',
# 'pop',
# 'remove',
# 'reverse',
# 'sort']
Usado en diccionarios anidados, resulta incluso más efectivo. Por otro lado, también
Profiling de un script
Esto puede ser realmente útil para ver donde se producen los cuellos de botella de
nuestro código. Se entiende por hacer profiling de un código, al analizar los tiempos
de ejecución de sus diferentes partes, para saber dónde se pierde más tiempo y actuar
en consecuencia.
Nota: cProfile es una implementación más rápida que profile ya que está escrito en
C.
Convertir CSV a json
Si ejecutas esto en el terminal, puedes convertir un CSV a json.
# Otra forma
print(list(itertools.chain(*lista)))
# Salida: [1, 2, 3, 4, 5, 6]
Construcciones en 1 línea
Otro código bastante interesante y que nos puede ahorrar varias líneas es el siguiente.
Tenemos el constructor de una clase con un determinado número de parámetros. En
vez de hacer self.nombre = nombre uno a uno, podemos reemplazarlo por la siguiente
línea.
class A(object):
def __init__(self, a, b, c, d, e, f):
self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})
Si quieres ver más construcciones de una línea, te recomendamos que leas el siguiente
enlace.
3.20 for/else
Los loops o bucles son una parte muy importante de cualquier lenguaje de progra-
mación, y por supuesto también existen en Python. Sin embargo, tienen algunas par-
ticularidades que mucha gente no conoce. A continuación las explicaremos.
Nota: Si buscas una explicación más completa de los bucles for en Python te recomen-
damos este post sobre el uso del for y este otro para el while.
Empecemos con un ejemplo básico de for, nada nuevo:
# Output: Manzana
# Plátano
# Mango
Un ejemplo sencillo en el que iteramos una lista que almacena diferentes cadenas con
for, y cambiamos su primera letra con una mayúscula. Veamos ahora otras de las
funcionalidades que tal vez no sean tan conocidas.
Los bucles for también tienen una cláusula else, y puede ser usada para ejecutar un
determinado fragmento de código cuando el bucle termina de manera natural. Por
manera natural se entiende que el bucle ha sido ejecutado tantas veces como había
sido planeado en su definición, y no termina por la sentencia break. Por lo tanto, si un
break rompe la ejecución del bucle, la cláusula else no será ejecutada.
Un ejemplo muy clásico en el uso de bucles, es iterar una determinada lista buscando
un elemento concreto. Si el elemento se encuentra, es habitual usar break para dejar
de buscar, ya que una vez hayamos encontrado lo que buscábamos, no tendría mucho
sentido seguir buscando.
Por otro lado, podría ocurrir también que se acabara de iterar la lista y que no se
hubiera encontrado nada. En este caso, el bucle terminaría sin pasar por la sentencia
break. Por lo tanto, una vez sabidos estos dos posibles escenarios, uno podría querer
saber cual ha sido la causa por la que el bucle ha terminado, si ha sido porque se
ha encontrado el elemento que se buscaba, o si por lo contrario se ha terminado sin
encontrar nada.
Veamos un ejemplo de la estructura del for/else:
3.20. for/else 54
Python Intermedio, Versión 0.1
Este ejemplo itera números de 2 a 10, y para cada uno busca un número que divida
de manera entera a cada uno de ellos. Si se encuentra, se rompe el primer bucle y se
continúa con el siguiente número.
Al ejemplo anterior podemos añadir un bloque else, para mostrar determinada in-
formación al usuario. Por ejemplo, si el número no es divisible por ninguno de sus
antecesores, significará que es un número primo.
• Tal vez quieras tener ciertos accesos a recursos muy a bajo nivel.
• O tal vez simplemente porque quieres hacerlo.
Sabido esto, vamos a ver tres formas de usar código C en Python.
3.21.1 CTypes
#include <stdio.h>
Ahora compilamos el código C en un archivo .so (DLL para Windows). Esto generará
el fichero adder.so.
#Para Linux
$ gcc -shared -Wl,-soname,adder -o adder.so -fPIC suma.c
#Para macOS
$ gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC suma.c
#Cargamos la librería
adder = CDLL('./adder.so')
suma_float = adder.suma_float
suma_float.restype = c_float
print "La suma de 5.5 y 4.1 es = ", str(suma_float(a, b))
La suma de 4 y 5 es = 9
La suma de 5.5 y 4.1 es = 9.60000038147
Vamos a explicar el ejemplo paso por paso. Por un lado tenemos el fichero de C, que
tampoco necesita explicación. Simplemente tenemos un par de funciones que suman
dos valores, la primera enteros y la segunda float.
Por otro lado, en el fichero de Python importamos el módulo ctypes. Después impor-
tamos la shared library que hemos creado usando la función CDLL. Una vez hayamos
hecho esto, las funciones definidas en la librería de C estarán disponibles en Python
a través de la variable adder. Por lo tanto, podemos por ejemplo llamar a suma_int
usando adder.suma_int() y pasando dos enteros como entrada. Esto producirá que la
función de C sea llamada con esos parámetros que hemos proporcionado y se nos de-
vuelva la salida. Es importante notar que podemos usar por defectos los tipos enteros
y cadenas.
Para otros tipos como booleanos o float, tenemos que especificarlo nosotros. Esto es
por lo que cuando pasamos los parámetros a la otra función que hemos definido que
usaba floats adder.suma_float(), tenemos que especificar el tipo con c_float. Como
se puede ver esta forma es relativamente sencilla de implementar, pero tiene limita-
ciones. Por ejemplo, no sería posible manipular objetos en C.
3.21.2 SWIG
Otra de las formas que existen para usar código C desde Python, es usando SWIG, que
viene de Simplified Wrapper and Interface Generator. En este método, es necesario crear
un nuevo interfaz (un fichero), que es usado como entrada para SWIG.
Es un método no muy conocido ya que en la mayoría de los casos es innecesariamente
complejo. Sin embargo, es un método bastante útil cuando tenemos cierto código en
C/C++ y queremos usarlo en diferentes lenguajes (no sólo en Python).
Ejemplo (De la web de SWIG )
Por un lado tenemos el código en C guardado en ejemplo.c, que tiene diferentes fun-
ciones y variables.
#include <time.h>
double My_variable = 3.0;
int fact(int n) {
if (n <= 1) return 1;
else return n*fact(n-1);
}
char *get_time()
{
time_t ltime;
time(<ime);
return ctime(<ime);
}
Por otro lado tenemos el fichero que actúa de interfaz, y que será el mismo para
cualquier lenguaje de programación, por lo que se puede reusar.
/* ejemplo.i */
%module ejemplo
%{
/* Pon aquí las cabeceras o las declaraciones como se muestra a continuación */
extern double My_variable;
extern int fact(int n);
extern int my_mod(int x, int y);
extern char *get_time();
%}
Lo compilamos.
Como podemos ver, el resultado que conseguimos con SWIG es el mismo, pero re-
quiere de un poco más des esfuerzo al tener que crear un fichero nuevo. Sin embargo
tal vez merezca la pena si queremos compartir código C con más de un lenguaje, ya
que este fichero de interfaz que hemos visto sólo necesitaría ser creado una vez.
Por último, la CPython API es una de las opciones más usadas, aunque no por su
simplicidad. Esto se debe aunque a pesar de ser más compleja que las anteriores vistas,
nos permite manipular objetos de Python en C.
Este método requiere que el código C sea escrito de manera específica para poder
ser usado desde Python. Todos los objetos de Python se representan como un
PyObject, y la cabecera Python.h nos proporciona diferentes funciones para manip-
ularlos. Por ejemplo, si el PyObject es un PyListType (es decir, una lista), podemos
usar PyList_Size() para calcular su longitud. Sería el equivalente a usar len(list)
en Python. En general, la mayoría de las funciones de Python están disponibles en
Python.h.
Ejemplo
Vamos a ver como escribir una extensión en C, que toma una lista y suma todos sus
elementos. Vamos a asumir que todos los elementos son números, como resulta evi-
dente.
Empecemos viendo la forma en la que nos gustaría poder usar la extensión de C que
vamos a crear.
l = [1,2,3,4,5]
print "La suma de la lista es " + str(l) + " = " + str(sumaLista.add(l))
El ejemplo anterior podría parecer un fichero normal y corriente de Python, que im-
porta y usa otro módulo llamado sumaLista. Sin embargo este módulo no está escrito
en Python sino en C. Esta es una de las ventajas principales, ya que en la parte de
Python no nos tenemos que preocupar de aprender nada nuevo o usar funciones ex-
tra. Se nos abstrae la librería de C como si fuera un módulo Python normal.
Lo siguiente es escribir el código sumaLista que será usado como hemos visto antes en
Python. Puede parecer un poco complicado pero ya verás como no lo es tanto.
//Fichero: adder.c
PyObject * listObj;
//Los argumentos de entrada son proporcionados como una tupla, los parseamos␣
,→ para
//obtener las variables. En este caso sólo se trata de una lista, que será
//referenciada por listObj.
if (! PyArg_ParseTuple( args, "O", &listObj))
return NULL;
/*
(continues on next page)
int n;
char *s;
PyObject* list;
PyArg_ParseTuple(args, "siO", &s, &n, &list);
setup(name='sumaLista', version='1.0', \
ext_modules=[Extension('sumaLista', ['adder.c'])])
Una vez realizado esto, ya podríamos usar el módulo que hemos creado en Python
como si de un módulo normal se tratase. Veamos como funciona:
l = [1,2,3,4,5]
print "La suma dela lista - " + str(l) + " = " + str(sumaLista.add(l))
Hemos explicado como crear tu primera extensión de C para Python usando la API
Python.h. Se trata de un método que puede parecer un poco complejo inicialmente,
pero una vez te acostumbras a el, puede ser realmente útil.
Existen otras formas de usar código C desde Python, como puede ser usar Cython,
pero se trata de un lenguaje un tanto diferente al típico Python, por lo que no lo
cubriremos aquí. No obstante te recomendamos que le eches un vistazo.
f = open('foto.jpg', 'r+')
jpgdata = f.read()
f.close()
Una de las razones por las que creemos conveniente explicar open() es por que es
habitual encontrarse el código anterior. Pues bien, hay un total de tres errores (o más
bien malas practicas). Al final de este capítulo entenderás porqué. Empecemos por lo
básico.
La función open devuelve lo que se conoce como file handle, y es dado por el sistema
operativo a tu aplicación de Python. Una vez has terminado de usar este file handle
(que te permite acceder al fichero) es importante devolverlo y cerrarlo. Esto se debe en
parte a que el sistema operativo tiene un número máximo de ficheros que puede tener
abiertos, y no tendría mucho sentido mantener uno abierto si ya no se está usando.
En el código anterior podemos ver como existe la llamada close(). La intención de
este código es buena, porque se cierra el fichero abierto, pero el problema es que sólo
se cerrará si f.read() funciona correctamente. Es decir, si existe un error en la función
f.read(), el programa terminará y el cierre del fichero no se producirá.
Por lo tanto, una de las mejores formas de asegurarnos de que el fichero se cierra
correctamente, pase lo que pase, es la siguiente haciendo uso de with.
Por desgracia, open() no soporta especificar el encoding en Python 2.x. Sin embargo, la
función io.open está disponible tanto en Python 2.x como 3.x y nos lo permite hacer.
Puedes pasar el tipo de encoding con la palabra encoding. Si no pasas ningún argu-
mento, se tomará el encoding por defecto. Suele ser una buena práctica indicar un
encoding específico. El utf-8 es uno de los más usados y con mayor soporte en nave-
gadores y lenguajes de programación. Por último, de la misma manera que se elige
encoding para leer, también se puede seleccionar para escribir un fichero.
Nota: Si usas utf-8 no deberías tener ningún problema con las ñ u otras letras como á
o ó. Sin embargo con otros encodings podrías tenerlos.
Llegados a este punto, tal vez te preguntes ¿y cómo se yo el enconding de un fichero?.
Bueno, existen varias maneras de hacerlo. Algunos editores de texto como Sublime
Text te lo suelen decir. Muchas veces los ficheros vienen con unos metadatos que
indican el encoding que es usado (como por ejemplo en las cabeceras HTTP).
Una vez sabido esto, vamos a escribir un programa que lee un fichero, y determina si
es una imagen JPG o no. (pista: Los ficheros JPG empiezan con la siguiente secuencia
de bits FF D8).
import io
if jpgdata.startswith(b'\xff\xd8'):
text = u'Es una imagen JPEG (%d bytes long)\n'
else:
text = u'No es una imagen JPEG (%d bytes long)\n'
Con esto ya hemos visto como abrir ficheros en diferentes modos, asegurándonos de
que son cerrados al terminar con ellos con with open. Hemos visto también el uso del
encoding y como podemos usar los metadatos de un fichero para saber si un archivo
contiene o no una imagen en JPEG.
Si te quedas con dudas, en estos post puedes leer más acerca de escribir ficheros y leer
ficheros en Python.
En algunas ocasiones puede ser normal querer desarrollar programas que puedan fun-
cionar en Python 2+ y Python 3+. Imagínate por ejemplo que has credo un módulo de
Python usado por cientos de personas, pero no todos tienen Python 2 o 3. En este caso
podrías tener dos opciones. La primera sería distribuir dos módulos, uno para Python
2 y otro para Python 3. La segunda sería modificar tu código para que funcionara con
ambas versiones.
En esta sección vamos a ver algunos de los trucos que puedes usar para que tus pro-
gramas sean compatibles con ambas versiones.
Imports del futuro
El primer y más importante método es usar los imports con __future__. Permite im-
portar funcionalidades de Python 3 en Python 2. Veamos un par de ejemplos:
Por ejemplo, los gestores de contexto o context managers se introdujeron en Python
2.6+. Para usarlos en Python 2.5 podrías hacer lo siguiente.
Por otro lado, print fue cambiado a una función en Python 3. Si quieres usarlo en
Python 2, podrías hacer lo siguiente haciendo uso de __future__:
print
# Salida:
import foo
# o también
from foo import bar
¿Sabes que otra cosa puedes hacer? Es posible también realizar lo siguiente:
try:
import urllib.request as urllib_request # Para Python 3
except ImportError:
import urllib2 as urllib_request # Para Python 2
Lo primero, estamos realizando los import dentro de un try/excep lee este post si tienes
dudas sobre try o except.
Hacemos esto ya que en Python 2 no existe el módulo urllib.request, por lo que si
intentamos importar tendremos un ImportError. La funcionalidad de urllib.request
es proporcionada por urllib2 en Python 2. Por lo tanto, si usamos Python 2 intentare-
mos importar urllib.request y como dará un error, importaremos urllib2.
También es importante mencionar el uso de la palabra clase as. Es una forma de asig-
nar un nombre al módulo importado, en nuestro caso urllib_request. Por lo tanto si
realizamos esto, todas las clases y métodos de urllib2 estarán disponibles con el alias
urllib_request.
Funciones obsoletas de Python 2
Otra cosa muy importante a tener en cuenta es que hay un total de 12 funciones de
Python 2 que han sido eliminadas de Python 3. Es importante asegurarse de que no
se usan en Python 2, para hacer que el código sea compatible con Python 3. A contin-
uación mostramos una forma que nos permite asegurarnos de que estas 12 funciones
no son usadas.
Ahora cada vez que usas una de las funciones que han sido eliminadas de Python 3,
tendrás un error NameError como el que se muestra a continuación.
apply()
# Salida: NameError: obsolete Python 2 builtin apply is disabled
3.24 Corrutinas
Las corrutinas son similares a los generadores pero tienen ciertas diferencias. Las prin-
cipales son las siguientes:
• Los generadores son productores de datos
• Las corrutinas son consumidores de datos
Antes de nada, vamos a revisar como se creaba un generador. Podemos hacerlo de la
siguiente manera:
3.24. Corrutinas 66
Python Intermedio, Versión 0.1
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
for i in fib():
print(i)
Es rápido y no consume demasiada memoria ya que genera los valores al vuelo (uno a
uno) en vez de almacenarlos todos en una lista. Ahora, si usamos yield en el anterior
ejemplo, tendremos una corrutina. Las corrutinas consumen los valores que le son
enviados. Un ejemplo muy sencillo sería un grep en Python:
def grep(pattern):
print("Buscando", pattern)
while True:
line = (yield)
if pattern in line:
print(line)
Pero espera, ¿qué es lo que devuelve yield? Bueno, en realidad lo que hemos hecho
es convertirlo en una corrutina. No contiene ningún valor inicialmente, sino que pro-
porcionamos esos valores externamente. Los valores son proporcionados usando el
método .send(). Aquí podemos ver un ejemplo:
search = grep('coroutine')
next(search)
# Salida: Buscando coroutine
search.send("I love you")
search.send("Don't you love me?")
search.send("I love coroutines instead!")
# Salida: I love coroutines instead!
Por lo tanto cuando enviamos una línea con .send(), si cumple con el pattern o patrón
que hemos indicado al llamar a la función grep() será impresa por pantalla.
Los valores enviados son accedidos por yield. Tal vez te preguntes sobre el uso de
next(). Es requerido para empezar la corutina. Al igual que los generators, las coruti-
nas no empiezan inmediatamente, sino que se ejecutan en respuesta a los métodos
__next__() y .send(). Por lo tanto tienes que ejecutar next() para que la ejecución
avance hasta la expresión yield.
Por otro lado, podemos cerrar la corrutina llamando al método .close() como se
muestra a continuación:
3.24. Corrutinas 67
Python Intermedio, Versión 0.1
search = grep('coroutine')
# ...
search.close()
Las coroutines van mucho más allá de lo que hemos explicado, por lo que te suge-
rimos que eches un vistazo a estar increíble presentación http://www.dabeaz.com/
coroutines/Coroutines.pdf de David Beazley (en Inglés).
@lru_cache(maxsize=32)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
fib.cache_clear()
3.25.2 Python 2+
En Python 2+, existen un par de formas de conseguir el mismo efecto. Puedes crear
crear tú mismo el mecanismo de caché, que dependerá de tus necesidades. Aquí te
mostramos un ejemplo genérico. Se crea un decorador que aplicado a una función hace
que al llamarla se busque en memo por los argumentos de entrada. Si no se encuentran,
llama a la función y los almacena.
def memoize(function):
memo = {}
@wraps(function)
def wrapper(*args):
try:
return memo[args]
except KeyError:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
@memoize
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(25)
Los gestores de contexto o context managers permiten asignar o liberar recursos de una
forma expresa. El ejemplo más usado es el with. Imagínate que tienes dos operaciones
relacionadas que te gustaría ejecutar con un determinado código de por medio. Los
gestores de contexto te permiten hacer precisamente esto. Veamos un ejemplo:
Al comparar los ejemplos anteriores podemos ver que gran cantidad de código
repetido es eliminado al usar with. La principal ventaja del uso de with es que se
asegura que el fichero se cierra, sin importar lo que hay en el bloque de código.
En general, los usos más comunes de los gestores de contexto son bloquear y liberar
recursos, como en el ejemplo que acabamos de ver con un fichero.
Vamos a ver como podemos implementar nuestro propio gestor de contexto. Esto sin
duda te permitirá entender que es lo que pasa por debajo.
Todo gestor de contextos tiene que tener al menos unos métodos __enter__ y un
__exit__ definidos. Vamos a crear nuestro propio gestor de contextos para abrir un
fichero:
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
self.file_obj.close()
Una vez definidos los métodos __enter__ y __exit__ en nuestra clase ya podemos
hacer uso del with de la misma forma que vimos anteriormente. Vamos a probarlo:
Nuestro método __exit__ acepta tres argumentos, más adelante veremos porqué.
Pero antes, analicemos lo que pasa por debajo:
1. La sentencia with almacena el método __exit__ de la clase File.
2. Llama al método __enter__ de la clase.
3. El método __enter__ abre el fichero y lo devuelve.
4. El fichero abierto es pasado a opened_file.
5. Escribimos en él usando .write().
6. La sentencia with llama al método __exit__.
7. Por último el método __exit__ cierra el fichero.
Veamos ahora todo lo que ocurre cuando with se encuentra con una excepción.
1. Se pasa el type, value y traceback del error al método __exit__.
2. Se delega en el __exit__ la gestión de la excepción.
3. Si __exit__ devuelve True, significa que la excepción ha sido manejada correcta-
mente.
4. Si algo diferente a True es devuelto, una excepción es lanzada por la sentencia
with.
En nuestro caso el método __exit__ devuelve None (ya que no hemos especificado
ningún valor de retorno). Por lo tanto y como hemos explicado, with lanzará la sigu-
iente excepción:
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
print("La excepción fue manejada")
self.file_obj.close()
return True
Podemos ver ahora como __exit__ devuelve True, por lo tanto with ya no lanza
ninguna excepción.
Esta no es la única forma de implementar Gestor de Contexto. Existe otra forma que
explicaremos en la siguiente sección.
@contextmanager
def open_file(name):
f = open(name, 'w')
try:
yield f
finally:
f.close()
La verdad que esta forma de implementar el gestor de contexto parece mucho más fácil
e intuitiva. Sin embargo esta forma requiere de algo de conocimiento previo acerca de
los generadores, decoradores y la sentencia yield. En este ejemplo no hemos capturado
ninguna excepción que pueda ocurrir.
Vamos a verlo parte por parte:
1. Python se encuentra con la palabra yield, por lo que crea un generador en vez
de una función normal.
2. Debido al uso del decorador, contexmanager es llamado con la función open_file
como argumento.
3. El decorador contextmanager devuelve el generador envuelto con el objeto
GeneratorContextManager.
4. El GeneratorContextManager es asignado a la función open_file. Por lo tanto,
cuando llamamos a la función open_file estamos en realidad usando un objeto
de la clase GeneratorContextManager.
Ahora que ya sabemos esto, podemos usar nuestro nuevo gestor de contexto de la
siguiente forma:
with open_file('some_file') as f:
f.write('hola!')