02 python Programación orientada a objetos y funcional
1. H
EL LENGUAJE DE PROGRAMACIÓN
PYTHON
Juan Ignacio Rodríguez de León
jileon en twitter
euribates @ gmail.com
Programación orientada a objetos
Programación funcional
2. Objetos y Clases
Las clases permiten que podamos definir
nuestros propios tipos de datos
Las Clases definen las propiedases
(atributos) y las capacidades y
comportamiento (métodos) general de
los nuevos tipos
Un objeto se crea o instancia a partir de
una clase
3. Objetos
Un objeto es una variable que
representa un caso particular dentro del
conjunto de posibles instancias de una
clase
De la misma forma que podemos
considerar al número 7 como una
instancia particular de la clase Numeros
Enteros
4. Creación de clases
Palabra reservada class
La clase más sencilla que podemos
pensar es:
>>> class X:
... pass
>>>
>>> class X:
... pass
>>>
Instanciamos un objeto usando el
nombre de la clase como si fuera una
función:
>>> x = X()
>>> print(x)
>>> x = X()
>>> print(x)
5. La clase Point
Inicializador: Método con el nombre
especial __init__
No es el constructor, pero casi
class Point:
def __init__(self, lat, lng):
self.latitud = lat
self.longitud = lng
x = Point(28.4779, -16.3118)
print(x.latitud, x.longitud)
class Point:
def __init__(self, lat, lng):
self.latitud = lat
self.longitud = lng
x = Point(28.4779, -16.3118)
print(x.latitud, x.longitud)
6. ¿self?
Se crea el objeto.
Inmediatamente a continuación, como
hemos visto, se llama al inicializador
– los dos parámetros que usamos al crear al
objeto son los mismos valores que se pasan
al método inicializador con los nombres lat
y lng
Pero ¿De donde sale el primer
parámetro, self? ¿Y qué representa?
7. Para programadores de C++ o Java
Para programadores de C++ o Java es la
variable "magica" this.
En Python se prefirió esta forma por
considerarla más explicita.
De igual manera, los atributos dentro de
la función tienen que venir precedidos
por el self., no hay alias mágicos para
los atributos de la instancia, para evitar
la ambigüedad.
8. Seguimos con self
Empezemos por la segunda pregunta:
self representa al propio objeto recien
creado
Este primer parámetro es lo que
diferencia a las funciones que ya
conociamos de los métodos:
– un método siempre tienen como primer
parámetro la instancia sobre la que está
siendo ejecutado.
9. Quien pone self ahí
Al definir métodos para una clase, hay
que reservar el primer parámetro para el
propio objeto
Al llamar al método desde la instancia,
Python ya se ocupa de poner el valor
correcto como primer parámetro
La tradición y la costumbre marcan que
este primer parámetro se llame self,
pero en realidad no existe obligación de
hacerlo (pero es conveniente hacerlo,
por legibilidad)
10. Herencia
Para poder hablar de clases y objetos
con propidad es necesario que haya
algún tipo de herencia
La herencia nos permite definir una clase
a base de refinar o modificar otra
(herencia simple) u otras (herencia
múltiple)
11. Herencia simple
Si una clase A deriva o hereda de una
clase B (o también se dice que la clase B
es una superclase de A):
– Entonces la clase A dispondrá, de entrada,
de todos los atributos y métodos de B
– Pero puede añadir más atributos y métodos
e incluso modificar o borrar los que ha
heredado.
12. Como declarar herencia en Python
La forma de expresar esta herencia en
python es
>>> class A(B):
... pass
>>> class A(B):
... pass
Si la clase modifique un método que ha
heredado, se dice que ha reescrito o
sobreescrito (override) el método.
13. Características de la herencia
Como los objetos instanciados de A tienen los
mismos atributos y métodos que B, deben
poder ser ser usados en cualquier sitio donde
se use una instacia de B
Entre A y B hay una relación es un tipo de
– B es un caso general de A
– O,si se prefiere, A es una especializacion de B
14. Polimorfismo
A puede sobreescribir un método f() de B
Si tenemos una lista con objetos de tipo A y
de tipo B mezclados, podemos invocar sin
miedo el método f() en todos ellos, con la
seguridad de que en cada caso se invocará al
método adecuado.
Esta capacidad se llama polimorfismo (del
griego Múltiples Formas)
15. Métodos o atributos privados
No existen en Python
Existe una convención de uso, por la cual
si un atributo o método empieza con el
carácter subrayado, ha de entenderse
que:
– Es de uso interno
– No deberías jugar con él a no ser que sepas
muy bien lo que estás haciendo
– Si en un futuro tu código deja de funcionar
porque has usado ese atributo o método, no
puedes culpar a nadie más que a ti mismo
16. Beneficios de usar clases/objetos
Reducir el tamaño del código evitando
repeticiones
– Si organizamos las herencias correctamente
en jerarquias, de mas genéricas a más
específicas, podemos compatibilizar el
código común de las primeras con el más
específico de las últimas
Encapsulamiento
Polimorfismo
Delegación de responsabilidades
17. super
¿Que pasa si A sobreescribe un método de B, pero
aun así ha de invocarlo?
En realidad es un caso muy común, A quiere hacer
lo mismo que B, y un poquito más.
Desde Python 2.2 hay una función super() que
nos ayuda a invocar el código de la clase (o clases)
de la que derivamos.
class A(B):
def f(self, arg):
super(A, self).f(arg)
...
class A(B):
def f(self, arg):
super(A, self).f(arg)
...
class A(B):
def f(self, arg):
super().f(arg)
class A(B):
def f(self, arg):
super().f(arg)
Python 2.x Python 3.x
18. Funciones auxiliares
isinstance(objeto, clase)
– nos devolverá verdadero si el objeto es una
instancia de una clase en particular, o de
alguna de sus subclases
issubclass(objeto, clase)
– nos devolverá verdadero si el objeto es una
instancia de una subclase de la clase
indicada.
19. Sobrecarga de operadores
Se puede, como en C++, sobreescribir
los operadores (operadores aritméticos,
acceso por índices, etc...) mediante una
sintaxis especial
Los métodos y atributos que empiezan y
acaban con un doble signo de subrayado
tiene por lo general un significado
especial.
20. Sobrecarga de operadores: len
Si en nuestra clase definimos un método
__len__(), podemos hacer que las
instancias de esa clase puedan ser
usadas con la función len()
Véase ejemplos/clases_02.py
21. Sobrecarga de operadores: índices
Si a una clase le añadimos los métodos
__setitem__ y __getitem__ podemos
hacer que se comporte como si fuera
una contenedor accesible mediante las
operaciones de índices
22. class A:
_Tabla = {
0: 'ninguno', 1: 'uno', 2: 'dos',
3: 'tres', 4: 'cuatro', 5: 'cinco',
6: 'umm... seis',
}
def __len__(self):
return 7 # por la cara
def __getitem__(self, index):
if 0 <= index < 7:
return self._Tabla[index]
else:
return 'Muchos'
def __setitem__(self, index, value):
pass
class A:
_Tabla = {
0: 'ninguno', 1: 'uno', 2: 'dos',
3: 'tres', 4: 'cuatro', 5: 'cinco',
6: 'umm... seis',
}
def __len__(self):
return 7 # por la cara
def __getitem__(self, index):
if 0 <= index < 7:
return self._Tabla[index]
else:
return 'Muchos'
def __setitem__(self, index, value):
pass
Ejemplos/clases_03.py
23. Sobrecarga de operadores: +/-
Supongamos que queremos escribir un módulo de
álgebra lineal y que definimos la clase Vector
Podríamos crear una función independiente para
sumar vectores:
v1 = Vector(2, 3)
v2 = Vector(-4, 2)
v3 = suma_vector(v1, v2)
v1 = Vector(2, 3)
v2 = Vector(-4, 2)
v3 = suma_vector(v1, v2)
● Pero es claramente mejor, más legible y bonito, poder
hacer
v3 = v1 + v2v3 = v1 + v2
25. Sobrecarga de operadores: +/-
Para eso definimos los métodos
especiales __add__ y __sub__ para
definir el comportamiento cuando se
sumen o resten dos instancias de nuesta
clase.
Véase ejemplos/clases_04.py
Existen muchos métodos especiales
A Guide to Python's Magic Methods:
– http://www.rafekettler.com/magicmethods.html
26. Excepciones
Errores sintáctis y excepciones
Información del error
Las excepciones se producen durante la
ejecución
– Pueden ser tratadas: “capturadas”
– Si no se capturan, el programa acaba
27. Tipos de excepciones
Según el error
ZeroDivisionError, ValueError, etc...
Como capturar excepciones: try/except
try:
a, b = 7, 0
c = a / b
except ZeroDivisionError:
print("No puedo dividir por cero")
try:
a, b = 7, 0
c = a / b
except ZeroDivisionError:
print("No puedo dividir por cero")
28. Tratamiento de excepciones
Puede haber varias cláusulas except,
para cada tipo de error
Una cláusula puede gestionar varios
errores
Puede haber una clausula except
general (No recomendado)
Podemos volver a “elevar” la excepcion
que hemos capturado
29. else en clausulas try/except
Similar al else del for/while
El código del else se ejecuta si y solo si:
– Se han ejecutado todas las líneas del try
– No se ha producido ninguna excepción
30. Argumento de la excepción
La excepción se representa con un valor que tiene los
detalles del error
Usando la palabra reservada as podemos almacenar
este valor
Captura errores en las llamadas a funciones
>>> def esto_falla():
... x = 1/0
...
>>> try:
... esto_falla()
... except ZeroDivisionError as detail:
... print('Detectado error', detail)
...
Detectado error: division by zero
>>>
>>> def esto_falla():
... x = 1/0
...
>>> try:
... esto_falla()
... except ZeroDivisionError as detail:
... print('Detectado error', detail)
...
Detectado error: division by zero
>>>
31. Legibilidad del código
con excepciones
Los programas en C suelen consistir en
una serie de llamadas a funciones
intercaladas con comprobaciones de
resultados
Con excepciones:
– La gestión de los errores no se entromete
con la función del algoritmo
– Está centralizada y aparte
32. Elevar excepciones
Podemos elevar nosotros mismos
excepciones, usando la palabra
reservada raise
Podemos definir nuestras propias
excepciones
– Derivadas de Exception
– Jerarquía de excepciones
33. Finally
Cláusula final, que se ejecutará siempre
Se hayan producido o no excepciones
Uso habitual:
– Liberación de recursos
– Operaciones de limpieza
– Cualquier código que tenga que ejecutarse
"si ó si"
34. Gestores de contexto: with
with nos permite "envolver" un bloque de
código con operaciones a ejecutar antes y
después del mismo
Simetría en las operaciones
Garantía de ejecución
Se pueden anidar
Ejemplos
– ficheros: open/close
– memoria: malloc/free
35. Más claro
try:
f = open('fichero.datos', 'r')
# proceso el fichero
n = len(f.readlines())
finally:
f.close()
try:
f = open('fichero.datos', 'r')
# proceso el fichero
n = len(f.readlines())
finally:
f.close()
with open('fichero.datos', 'r') as f:
... # proceso el fichero
... n = len(f.readlines())
with open('fichero.datos', 'r') as f:
... # proceso el fichero
... n = len(f.readlines())
En vez de hacer esto:
Hacemos esto:
36. Como funciona with (1)
Usando Gestores de contexto
Son objetos que saben lo que hay que
hacer antes y después de usar otro
objeto
El generador de contexto de file sabe
que hay que cerrar el archivo
Imposible olvidarse de cerrarlo
37. Como funciona with (2)
1) Evaluación y obtención del gestor de
contexto
2) Se carga __exit__()
3) Se ejecuta __enter__() (si devuelve un
valor y se ha usado as, se asigna a la
variable)
4) Se ejecuta el bloque
5) Se llama a __exit__() (con información
de errores, si hubiera)
38. Iteradores
Nuestras clases y objetos pueden ser
iterables
Como funciona for internamente:
– Se llama a iter() pasándole lo que quiera
que sea que vamos a iterar
– Obtenemos un iterador, es decir, un objeto
con un metodo next()
– Llamamos a next() repetidas veces...
– Hasta que termina: se eleva StopIteration
39. Ejemplo
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
it.next()
StopIteration
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
it.next()
StopIteration
40. Es fácil añadir “iterabilidad”
Definir un método con el nombre
__iter__() que devuelva un objeto
Este objeto debe implementar un método
next()
– Cada vez que se llame a next(), este debe
devolver el siguiente elemento
– A no ser que no queden, entonces, eleva
StopIteration
Nuestra clase puede ser su propio iterador:
basta con definir next() y en __iter__
devolver self
41. Ejercicio
Crear una clase CuentaAtras, que sea
iterable y que, ejem, cuente hacia atrás
hasta llegar al cero.
42. Solución
class CuentaAtras:
def __init__(self, tope):
self.tope = tope
def __iter__(self):
self.counter = self.tope
return self
def next(self): return self.__next__() # python 2.7
def __next__(self):
result = self.counter
self.counter -= 1
if result < 0:
raise StopIteration
return result
class CuentaAtras:
def __init__(self, tope):
self.tope = tope
def __iter__(self):
self.counter = self.tope
return self
def next(self): return self.__next__() # python 2.7
def __next__(self):
result = self.counter
self.counter -= 1
if result < 0:
raise StopIteration
return result
43. Generadores
Forma sencilla y potente de crear iteradores
Como una función, pero devuelven resultados con
yield, en vez de return
Cada vez que se llama a next(), el generador
continua a partir de donde se quedó
Recuerda su estado: valores de las variables,
última línea que se ejecutó, etc...
44. Cuenta Atras (como generador)
>>> def cuenta_atras(n):
... while n >= 0:
... yield n
... n -= 1
...
>>> for i in cuenta_atras(5): print(i)
...
5
4
3
2
1
0
>>>
>>> def cuenta_atras(n):
... while n >= 0:
... yield n
... n -= 1
...
>>> for i in cuenta_atras(5): print(i)
...
5
4
3
2
1
0
>>>
45. Ventajas
Igual potencia que un iterador
Solo por usar yield ya se sabe que es
un generador
Normalmente más fáciles de escribir
Generación automática de next() y de
__iter__()
Eleva automáticamente StopIteration
46. Ejemplos de
generadores/iteradores
El módulo os.path, como veremos más
adelante, tiene una función walk, que es
un generador que nos permite recorrer
un árbol de directorios
El módulo itertools define una funcion
combinations que nos da las
combinaciones de m elementos
tomandos de n en n
47. Ejemplo de combinaciones
Combinaciones tomando los cuatro
elementos ABCD
– De uno en uno:
● A, B, C, D
– De dos en dos:
● AB, AC, AD, CB, CD, BD
– De tres en tres:
● ACB, ACD, ABD, CBD
– De cuatro en cuatro:
● ABCD
50. Ajustar cuentas
Estan investigando un caso
de un ex-tesorero y una
doble facturación
Hay una serie de ingresos
por un lado
Y una serie de pagos por otro
Demostrar que la serie de
pagos se corresponden,
sumadas, con los ingresos.
53. ¿Como podemos hacerlo?
Empezar por uno de los ingresos
– Tomar las combinaciones de pagos y
tomarlas de una en una. Ver si sumadas
coinciden con el ingreso
– Tomar las combinaciones de pagos y
tomarlas de dos en dos. Ver si sumadas
coinciden con el ingreso
– Seguir asi buscando combinaciones hasta
que una coincida. Cuando se encuentra,
retirar esos pagos de la lista de pagos y
seguir con el siguiente ingreso
54. Herramientas
Conseguir las combinaciones usando
itertoos.combinations.
La función sum() nos suma los
elementos de una secuencia
Empezar con una versión minima del
problema: 2 ingresos, 4 pagos, por
ejemplo.
Solución en:
– ejemplos/facturacion_b.py
56. facturacion_b.py (2)
for ingreso in ingresos:
solucion = None
for size in range(1, len(pagos)+1):
# Probando combinaciones de size elementos
for a_probar in itertools.combinations(pagos, size):
if sum(a_probar) == ingreso:
print('Encontrada una solución:')
solucion = tuple(a_probar)
print(*['{0:f}'.format(d) for d in solucion],
sep=' + ',
end=' = ')
print(ingreso)
break
if solucion:
for pago in solucion:
pagos.remove(pago)
for ingreso in ingresos:
solucion = None
for size in range(1, len(pagos)+1):
# Probando combinaciones de size elementos
for a_probar in itertools.combinations(pagos, size):
if sum(a_probar) == ingreso:
print('Encontrada una solución:')
solucion = tuple(a_probar)
print(*['{0:f}'.format(d) for d in solucion],
sep=' + ',
end=' = ')
print(ingreso)
break
if solucion:
for pago in solucion:
pagos.remove(pago)
58. Programación funcional
Las funciones solo son otro tipo de
variable
Todo lo que se puede hacer con una
variable, se puede hacer con una función:
– funciones como parámetros
– funciones dentro de estructuras de datos
– funciones como resultados
“Las funciones son objetos de primera
clase”
59. Funciones Lambda λ
Crear pequeñas funciones anónimas
lambda <argumentos>: <expresion>
Función que suma los dos parámetros
que se le pasan:
– lambda(x,y): x+y
No hace falta especificar return
Azucar sintáctico para una definición de
función normal
60. filter
Primer parámetro: una función
Segundo parámetro: una secuencia
Devuelve: otra secuencia en la que se
estan sólo aquellos valores de la
secuencia original para los que el
resultado de aplicarles la función es
True
62. los primeros 200 números
que son divisibles por 5 y por 7
>>> def div57(x):
... return x % 5 == 0 and x % 7 == 0
...
>>> for i in filter(div57, range(1, 201)):
... print(i)
...
35
70
105
140
175
>>>
>>> def div57(x):
... return x % 5 == 0 and x % 7 == 0
...
>>> for i in filter(div57, range(1, 201)):
... print(i)
...
35
70
105
140
175
>>>
63. map
Primer parámetro: una función
Segundo parámetro: una secuencia
Devuelve: otra secuencia, compuesta
por los resultados de llamar a la función
en cada uno de los elementos de la
secuencia original
65. map (2)
Podemos pasar más de una secuencia
La función pasada como parámetro
debe aceptar tantos parámetros como
secuencias haya
Ejercicio: media de los datos de otras
dos listas
66. media de los datos de dos listas
>>> l1 = [123, 45, 923, 2, -23, 55]
>>> l2 = [9, 35, 87, 75, 39, 7]
>>> def media(a, b): return (a + b) / 2
...
>>> map(media, l1, l2)
[66.0, 40.0, 505.0, 38.5, 8.0, 31.0]
>>>
>>> l1 = [123, 45, 923, 2, -23, 55]
>>> l2 = [9, 35, 87, 75, 39, 7]
>>> def media(a, b): return (a + b) / 2
...
>>> map(media, l1, l2)
[66.0, 40.0, 505.0, 38.5, 8.0, 31.0]
>>>
67. reduce
Primer parámetro: una función
Segundo parámetro: una secuencia
Devuelve: un único valor
– la función que se pasa como parámetro tiene
que aceptar dos valores, y retornar uno
– Se calcula el resultado de aplicar la función a los
dos primeros valores de la secuencia
– A continuación, se aplica de nuevo la función,
usando como parámetros el dato anterior y al
tercer elemento de la secuencia
– Así hasta acabar la secuencia original
71. Ejercicio: sumar los valores
de una lista
>>>
>>> def suma(x,y): return x+y
...
>>> reduce(suma, range(1, 11))
55
>>>
>>>
>>> def suma(x,y): return x+y
...
>>> reduce(suma, range(1, 11))
55
>>>
No se debe usar este modo de realizar sumas,
porque esta es una necesidad tan común que ya
existe una función incorporada para ello:
sum(seq), que funciona exactamente igual, pero
más rápido al estár implementada en C.
72. Compresión de listas
Forma muy expresiva de crear listas
Usos comunes
– Crear una lista cuyos elementos son resultado
de aplicar una serie de operaciones a otra
secuencia
– Crear una sebsecuencia de aquellos elementos
que cumplan una determinada condición
En resumen, nos permiten hacer lo mismo
que map o filter, pero de forma más
legible.
73. 10 Números cuadrados
Podemos crear una lista con los
cuadrados de los 10 primeros números
así:
>>> squares = []
>>> for x in range(11):
... squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>>
>>> squares = []
>>> for x in range(11):
... squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>>
74. 10 Números cuadrados
O así, con map
squares = map(lambda x: x**2, range(10))squares = map(lambda x: x**2, range(10))
75. 10 Números cuadrados
Con comprensión de listas es aun más
fácil
squares = [x**2 for x in range(11)]squares = [x**2 for x in range(11)]
76. Anatomía de una C.L.
Corchete [
Expresión
Cláusula for
Cero, una o más cláusulas if
Corchete ]
77. Ejercicio
¿Cuáles de los primeros 1500 números
enteros cumplen la condición de que su
cubo acaba en 272?
– str() convierte un número en texto
– endswith() es un metodo de los textos que
devuelve True si la cadena de texto sobre la
que se aplica acaba en el texto indicado
como parámetro:
● 'hola'.endswith('a') → True
78. Respuesta
[x for x in range(501) if str(x**3).endswith('272')][x for x in range(501) if str(x**3).endswith('272')]
[238, 488][238, 488]
79. Expresiones generadoras
Muy similar a una conprensión de lista
Pero devuelve un generador, no una lista
La sintaxis es idéntica, sustituyendo los
corchetes por paréntesis
– con la lista obtenemos todos los elementos
ya generados (y, por tanto, consumiendo
memoria)
– El generador nos irá dando los valores de
uno en uno (lazy evaluation)
80. Ejemplo
>>> s = [x**2 for x in range(11)]
>>> s # es una lista
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> s = (x**2 for x in range(11))
>>> s # es un generador
<generator object <genexpr> at 0xb74588ec>
>>> s.next()
0
>>> for i in s: print(i)
...
1
4
[...]
81
100
>>>
>>> s = [x**2 for x in range(11)]
>>> s # es una lista
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> s = (x**2 for x in range(11))
>>> s # es un generador
<generator object <genexpr> at 0xb74588ec>
>>> s.next()
0
>>> for i in s: print(i)
...
1
4
[...]
81
100
>>>
81. Comprensión de diccionarios
Crear diccionarios a partir de otras
fuentes de datos
Sintaxis similar, pero cambiando
corchetes/paréntesis por llaves: {}
La expresión tienen que tener la
forma <clave>:<valor>
82. Ejemplos
>>> d = {x:x**2 for x in range(5)}
>>> d
{1: 1, 0: 0, 3: 9, 2: 4, 4: 16}
>>> d = {x:x**2 for x in range(5) if x % 2 == 0}
>>> d
{0: 0, 2: 4, 4: 16}
>>> print({i : chr(65+i) for i in range(4)})
{0 : 'A', 1 : 'B', 2 : 'C', 3 : 'D'}
>>>
>>> d = {x:x**2 for x in range(5)}
>>> d
{1: 1, 0: 0, 3: 9, 2: 4, 4: 16}
>>> d = {x:x**2 for x in range(5) if x % 2 == 0}
>>> d
{0: 0, 2: 4, 4: 16}
>>> print({i : chr(65+i) for i in range(4)})
{0 : 'A', 1 : 'B', 2 : 'C', 3 : 'D'}
>>>
83. Conprensión de conjuntos
Definir un conjunto a partir de otros
valores
Igual que con los diccionarios, pero la
expresión no va en la forma
<clave>:<valor>, sino como una
expresión simple
NO se puede crear así un diccionario
vacio (Crearía un diccionario)
84. Ejemplo
>>> s = {'a', 'b', 'c'}
>>> s
set(['a', 'c', 'b'])
>>> s = {str(x**2) for x in range(7)}
>>> type(s)
<type 'set'>
>>> s
set(['25', '16', '36', '1', '0', '4', '9'])
>>>
>>> s = {'a', 'b', 'c'}
>>> s
set(['a', 'c', 'b'])
>>> s = {str(x**2) for x in range(7)}
>>> type(s)
<type 'set'>
>>> s
set(['25', '16', '36', '1', '0', '4', '9'])
>>>
85. Decoradores
Funciones como objetos de primer nivel
Las funciones se pueden almacenar,
pasar como parámetros...
… o ser devueltas como resultado de
una función
Una función puede devolver otra función
88. ¿Qué es un decorador?
Sabiendo que esto es posible, un
decorador es:
– Una función que acepta como parámetro
una función, y devuelve otra función, que
normalmente sustituirá a la original.
– Es decir, un decorador nos permite
modificar una función (Normalmente
haciendo algo antes, o después, o ambas
cosas)
90. ¿Para qué sirve un decorador?
El uso de decoradores se enfoca a
resolver el siguiente problema:
– Tenemos un conjunto de funciones
– Queremos que todas ellas hagan una nueva
cosa, algo por lo general ajeno al propio
comportamiento de la función, y que todas
lo hagan por igual.
– En otras palabras, queremos añadir una
funcionalidad horizontal.
91. Ejemplo de situación
Supongamos que tenemos un conjunto
de funciones a(), b(),..., z(), cada una
de ellas con sus parámetros,
particularidades, etc...
Queremos ahora, con el mínimo trabajo
posible, que cada función escriba en un
fichero log cuando empieza a trabajar y
cuanto termina.
92. Opción A (de “A lo bruto”)
Reescribir cada una de las funciones
def a():
# código de a
def a():
# código de a
Pasar de esto:
def a():
with open('/tmp/log.txt', 'a') as log:
log.write('Empieza la función an')
# codigo de a
with open('/tmp/log.txt', 'a') as log:
log.write('Acaba la función an')
def a():
with open('/tmp/log.txt', 'a') as log:
log.write('Empieza la función an')
# codigo de a
with open('/tmp/log.txt', 'a') as log:
log.write('Acaba la función an')
A esto:
93. Pegas de la opción A
Sencillo, pero trabajoso
Hay que reescribir mucho código
El tamaño del código aumenta
La lógica de las funciones queda
difuminada con todas esas llamadas a
escribir el log
Si queremos cambiar la información del
log, (incluir fecha y hora, p.e.) hay que
volver a modificar todas las funciones
94. Opción D (De “decoradores”)
Intenta solucionar estos problemas
Un decorador coge las función original,
(a(), b(),..., z() en nuestro caso), la
modifica y la reemplaza
Ahora, cuando se llama a a(), se invoca
en realidad a nuestra versión modificada
(que a su vez invocará a la a() original)
95. Decorador “logged”
Para el ejemplo de log, primero creamos
una función decoradora, que llamaramos
logged()
Para simplificar, en vez de escribir a un
fichero log nos limitaremos a hacer dos
prints, uno antes de que empieze la
función y otro después
96. Código de logged
def logged(func):
def inner(* args, **kwargs):
print('Empieza la función {}'.
format(func.__name__))
func(*args, **kwargs)
print('Termina la función {}'.
format(func.__name__))
return inner
def logged(func):
def inner(* args, **kwargs):
print('Empieza la función {}'.
format(func.__name__))
func(*args, **kwargs)
print('Termina la función {}'.
format(func.__name__))
return inner
97. Aplicación del decorador
def a(): print('Soy a()')
def b(): print('Soy b()')
b = logged(b)
@logged
def c(): print('Soy c()')
@logged
def d(msg):
print('Soy d y digo: {}'.format(msg))
a()
b()
c()
d('Hola, mundo')
def a(): print('Soy a()')
def b(): print('Soy b()')
b = logged(b)
@logged
def c(): print('Soy c()')
@logged
def d(msg):
print('Soy d y digo: {}'.format(msg))
a()
b()
c()
d('Hola, mundo')
98. Aplicación de decoradores
La forma:
@logged
def c(): print('Soy c()')
@logged
def c(): print('Soy c()')
Es azucar sintáctico para:
def c(): print('Soy c()')
c = logged(c)
def c(): print('Soy c()')
c = logged(c)
La forma más cómoda de aplicar el
decorador es poner el símbolo @ y el
nombre del decorador antes de la definición
de la función
99. Ventaja de los decoradores
Hay que tocar el código de cada función, si, pero el
cambio es mínimo: añadir el decorador con el
simbolo @
El código no se repite. No hay aumento apreciable
de tamaño del mismo
El código interno de las funciones decoradas no se
ve perturbado por la nueva funcionalidad.
Podemos añadir nuevas características a las
funciones "logeadas" modificando solo una cosa: el
decorador