SlideShare uma empresa Scribd logo
1 de 99
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
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
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
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)
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)
¿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?
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.
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.
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)
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)
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.
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.
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
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)
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
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
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
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.
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.
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
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
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
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
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return 'Vector({}, {})'.format(self.x, self.y)
def __add__(self, other):
return Vector(
self.x + other.x,
self.y + other.y
)
def __sub__(self, other):
return Vector(
self.x - other.x,
self.y - other.y
)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return 'Vector({}, {})'.format(self.x, self.y)
def __add__(self, other):
return Vector(
self.x + other.x,
self.y + other.y
)
def __sub__(self, other):
return Vector(
self.x - other.x,
self.y - other.y
)
ejemplos/clases_04.py
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
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
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")
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
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
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
>>>
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
Elevar excepciones
 Podemos elevar nosotros mismos
excepciones, usando la palabra
reservada raise
 Podemos definir nuestras propias
excepciones
– Derivadas de Exception
– Jerarquía de excepciones
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"
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
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:
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
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)
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
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
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
Ejercicio
 Crear una clase CuentaAtras, que sea
iterable y que, ejem, cuente hacia atrás
hasta llegar al cero.
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
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...
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
>>>
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
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
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
La Fiscalia Anticorrupción
nos pide ayuda
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.
Los ingresos
1910.00 €
4090.20 €
1945.45 €
Los pagos
1404.93 €
207.68 €
297.39 €
1816.42 €
153.56 €
1286.85 €
322.90 €
175.04 €
335.43 €
259.74 €
301.28 €
1384.43 €
¿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
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
facturacion_b.py (1)
from decimal import Decimal
import itertools
ingresos = [
Decimal('4090.20'),
Decimal('1910.00'),
Decimal('1945.45'),
]
pagos = [
Decimal('1404.93'), Decimal('207.68'), Decimal('297.39'),
Decimal('1816.42'), Decimal('153.56'), Decimal('1286.85'),
Decimal('322.9'), Decimal('175.04'), Decimal('335.43'),
Decimal('259.74'), Decimal('301.28'), Decimal('1384.43'),
]
from decimal import Decimal
import itertools
ingresos = [
Decimal('4090.20'),
Decimal('1910.00'),
Decimal('1945.45'),
]
pagos = [
Decimal('1404.93'), Decimal('207.68'), Decimal('297.39'),
Decimal('1816.42'), Decimal('153.56'), Decimal('1286.85'),
Decimal('322.9'), Decimal('175.04'), Decimal('335.43'),
Decimal('259.74'), Decimal('301.28'), Decimal('1384.43'),
]
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)
Solución
1816.42 + 153.56
+ 1286.85 + 322.9
+ 175.04 + 335.43 = 4090.20
1404.93 + 207.68 + 297.39 = 1910.00
259.74 + 301.28 + 1384.43 = 1945.45
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”
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
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
Ejemplo
Calcular los primeros 200
números que son divisibles
por 5 y por 7
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
>>>
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
cubos de los 10 primeros números
>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>>
>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>>
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
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]
>>>
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
Ejercicio: sumar los valores
de una lista
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
>>>
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
>>>
Suma(1,2) = 3
Suma(3,3) = 6
Suma(6,4) = 10
Suma(10,5) = 15
Suma(15,6) = 21
Suma(21,7) = 28
Suma(28,8) = 36
Suma(36,9) = 45
Suma(45,10) = 55
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.
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.
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]
>>>
10 Números cuadrados
 O así, con map
squares = map(lambda x: x**2, range(10))squares = map(lambda x: x**2, range(10))
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)]
Anatomía de una C.L.
 Corchete [
 Expresión
 Cláusula for
 Cero, una o más cláusulas if
 Corchete ]
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
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]
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)
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
>>>
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>
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'}
>>>
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)
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'])
>>>
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
Suena raro...
Una función …
… puede devolver …
… otra función
No es tan raro, veamos un ejemplo
>>> def dame_una_funcion_incremento(inc):
... def funcion_a_retornar(x):
... return x + inc
... return funcion_a_retornar
...
>>> inc3 = dame_una_funcion_incremento(3)
>>> inc3(6)
9
>>> inc47 = dame_una_funcion_incremento(47)
>>> inc47(3)
50
>>> def dame_una_funcion_incremento(inc):
... def funcion_a_retornar(x):
... return x + inc
... return funcion_a_retornar
...
>>> inc3 = dame_una_funcion_incremento(3)
>>> inc3(6)
9
>>> inc47 = dame_una_funcion_incremento(47)
>>> inc47(3)
50
¿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)
Referencia friki totalmente gratuita
¿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.
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.
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:
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
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)
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
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
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')
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
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

Mais conteúdo relacionado

Mais procurados

Herencia - Programación Orientada a Objetos
Herencia - Programación Orientada a ObjetosHerencia - Programación Orientada a Objetos
Herencia - Programación Orientada a Objetos
Mario Villaseñor
 
Diagramas de objetos
Diagramas de objetosDiagramas de objetos
Diagramas de objetos
still01
 

Mais procurados (20)

Programacion Orientada a Objetos en python
Programacion Orientada a Objetos en pythonProgramacion Orientada a Objetos en python
Programacion Orientada a Objetos en python
 
Clases y objetos en Java
Clases y objetos en JavaClases y objetos en Java
Clases y objetos en Java
 
Programacion Orientada a Objetos
Programacion Orientada a ObjetosProgramacion Orientada a Objetos
Programacion Orientada a Objetos
 
Herencia - Programación Orientada a Objetos
Herencia - Programación Orientada a ObjetosHerencia - Programación Orientada a Objetos
Herencia - Programación Orientada a Objetos
 
Programación 3: listas enlazadas
Programación 3: listas enlazadasProgramación 3: listas enlazadas
Programación 3: listas enlazadas
 
Diagrama de clases
Diagrama de clasesDiagrama de clases
Diagrama de clases
 
Encapsulamiento en JAVA-NETBEANS
Encapsulamiento en JAVA-NETBEANSEncapsulamiento en JAVA-NETBEANS
Encapsulamiento en JAVA-NETBEANS
 
Programación Orientada a Objetos - herencia y polimorfismo
Programación Orientada a Objetos - herencia y polimorfismoProgramación Orientada a Objetos - herencia y polimorfismo
Programación Orientada a Objetos - herencia y polimorfismo
 
Unidad 4 a HERENCIA, CLASES ABSTRACTAS, INTERFACES Y POLIMORFISMO . UML
Unidad 4 a HERENCIA, CLASES ABSTRACTAS, INTERFACES Y POLIMORFISMO . UMLUnidad 4 a HERENCIA, CLASES ABSTRACTAS, INTERFACES Y POLIMORFISMO . UML
Unidad 4 a HERENCIA, CLASES ABSTRACTAS, INTERFACES Y POLIMORFISMO . UML
 
Programación Orientada a Objetos
Programación Orientada a ObjetosProgramación Orientada a Objetos
Programación Orientada a Objetos
 
Diagrama de clases
Diagrama de clasesDiagrama de clases
Diagrama de clases
 
Ejercicios uml
Ejercicios umlEjercicios uml
Ejercicios uml
 
8b Curso de POO en java - paso de diagrama clases a java 1
8b Curso de POO en java - paso de diagrama clases a java 18b Curso de POO en java - paso de diagrama clases a java 1
8b Curso de POO en java - paso de diagrama clases a java 1
 
Diagramas de objetos
Diagramas de objetosDiagramas de objetos
Diagramas de objetos
 
3.4. Logica de predicados
3.4. Logica de predicados3.4. Logica de predicados
3.4. Logica de predicados
 
Programación 3: arrays (arreglos) y vectores
Programación 3: arrays (arreglos) y vectoresProgramación 3: arrays (arreglos) y vectores
Programación 3: arrays (arreglos) y vectores
 
Encapsulamiento poo
Encapsulamiento pooEncapsulamiento poo
Encapsulamiento poo
 
Presentacion de Modelo entidad -relación de Base de Datos
Presentacion de Modelo entidad -relación de Base de Datos Presentacion de Modelo entidad -relación de Base de Datos
Presentacion de Modelo entidad -relación de Base de Datos
 
1. introduccion a la programación orientada a objeto (poo)
1.  introduccion a la programación orientada a objeto (poo)1.  introduccion a la programación orientada a objeto (poo)
1. introduccion a la programación orientada a objeto (poo)
 
Introduccion a python 3
Introduccion a python 3Introduccion a python 3
Introduccion a python 3
 

Semelhante a 02 python Programación orientada a objetos y funcional

programacion orientada a objetos
programacion orientada a objetosprogramacion orientada a objetos
programacion orientada a objetos
jent46
 
Explicacion de interface
Explicacion de interfaceExplicacion de interface
Explicacion de interface
Boris Salleg
 
Herencia en java
Herencia en javaHerencia en java
Herencia en java
jbersosa
 
Php orientado a_objetos
Php orientado a_objetosPhp orientado a_objetos
Php orientado a_objetos
Jose Ney Quil
 
Java morld cap2 [CURSO JAVA]
Java morld cap2 [CURSO JAVA]Java morld cap2 [CURSO JAVA]
Java morld cap2 [CURSO JAVA]
Hack '
 

Semelhante a 02 python Programación orientada a objetos y funcional (20)

U8.- Programacion Orientada a objetos II (2).pdf
U8.- Programacion Orientada a objetos II (2).pdfU8.- Programacion Orientada a objetos II (2).pdf
U8.- Programacion Orientada a objetos II (2).pdf
 
Java
JavaJava
Java
 
programacion orientada a objetos
programacion orientada a objetosprogramacion orientada a objetos
programacion orientada a objetos
 
Guia poo
Guia pooGuia poo
Guia poo
 
Guia poo
Guia pooGuia poo
Guia poo
 
Guia poo php
Guia poo phpGuia poo php
Guia poo php
 
C2 clases en java
C2 clases en javaC2 clases en java
C2 clases en java
 
Programación ii presentación tema 5
Programación ii presentación tema 5Programación ii presentación tema 5
Programación ii presentación tema 5
 
PROGRAMACIÓN ORIENTADA A OBJETOS
PROGRAMACIÓN ORIENTADA A OBJETOSPROGRAMACIÓN ORIENTADA A OBJETOS
PROGRAMACIÓN ORIENTADA A OBJETOS
 
Cuarta sesion herencia y polimorfismo-explicacion anibal
Cuarta sesion herencia y polimorfismo-explicacion anibalCuarta sesion herencia y polimorfismo-explicacion anibal
Cuarta sesion herencia y polimorfismo-explicacion anibal
 
Poo
PooPoo
Poo
 
Net1 oop vbnet
Net1 oop vbnetNet1 oop vbnet
Net1 oop vbnet
 
Explicacion de interface
Explicacion de interfaceExplicacion de interface
Explicacion de interface
 
Programacion
ProgramacionProgramacion
Programacion
 
Herencia en java
Herencia en javaHerencia en java
Herencia en java
 
Php orientado a_objetos
Php orientado a_objetosPhp orientado a_objetos
Php orientado a_objetos
 
Java morld cap2 [CURSO JAVA]
Java morld cap2 [CURSO JAVA]Java morld cap2 [CURSO JAVA]
Java morld cap2 [CURSO JAVA]
 
Semana9b Vbr
Semana9b VbrSemana9b Vbr
Semana9b Vbr
 
Presentacion de clases en c#
Presentacion de clases en c#Presentacion de clases en c#
Presentacion de clases en c#
 
03 java poo_parte_2
03 java poo_parte_203 java poo_parte_2
03 java poo_parte_2
 

Mais de Juan Rodríguez

Mais de Juan Rodríguez (20)

Estados imposibles y como evitarlos
Estados imposibles y como evitarlosEstados imposibles y como evitarlos
Estados imposibles y como evitarlos
 
Introduction to BDD (Behavior-Driven Development)
Introduction to BDD (Behavior-Driven Development)Introduction to BDD (Behavior-Driven Development)
Introduction to BDD (Behavior-Driven Development)
 
Vue.js: El framework javascript para muggles
Vue.js: El framework javascript para mugglesVue.js: El framework javascript para muggles
Vue.js: El framework javascript para muggles
 
Introducción a jupyter (antes i python notebook)
Introducción a jupyter (antes i python notebook)Introducción a jupyter (antes i python notebook)
Introducción a jupyter (antes i python notebook)
 
Acceder a C desde Python (O viceversa)
Acceder a C desde Python (O viceversa)Acceder a C desde Python (O viceversa)
Acceder a C desde Python (O viceversa)
 
Viaje al centro de la locura con Javascript
Viaje al centro de la locura con JavascriptViaje al centro de la locura con Javascript
Viaje al centro de la locura con Javascript
 
Una imagen vale más que mil mentiras
Una imagen vale más que mil mentirasUna imagen vale más que mil mentiras
Una imagen vale más que mil mentiras
 
Introducción a DSL (Lenguajes Específicos de Dominios) con Python
Introducción a DSL (Lenguajes Específicos de Dominios) con PythonIntroducción a DSL (Lenguajes Específicos de Dominios) con Python
Introducción a DSL (Lenguajes Específicos de Dominios) con Python
 
Presentación estetoscopio
Presentación estetoscopioPresentación estetoscopio
Presentación estetoscopio
 
Charla introducción a RaspberryPI
Charla introducción a RaspberryPICharla introducción a RaspberryPI
Charla introducción a RaspberryPI
 
Guia (breve) de supervivencia a python 3
Guia (breve) de supervivencia a python 3Guia (breve) de supervivencia a python 3
Guia (breve) de supervivencia a python 3
 
¡A todo Kanban! ~ Introducción a kanban
¡A todo Kanban! ~ Introducción a kanban¡A todo Kanban! ~ Introducción a kanban
¡A todo Kanban! ~ Introducción a kanban
 
01 el lenguaje Python
01 el lenguaje Python01 el lenguaje Python
01 el lenguaje Python
 
00 introducción a Python
00 introducción a Python00 introducción a Python
00 introducción a Python
 
Taller de introducción al desarrollo web con Django
Taller de introducción al desarrollo web con DjangoTaller de introducción al desarrollo web con Django
Taller de introducción al desarrollo web con Django
 
Presentación appy/pod
Presentación appy/podPresentación appy/pod
Presentación appy/pod
 
Presentacion Google Mini Adeje 16 Oct 2008
Presentacion Google Mini Adeje 16 Oct 2008Presentacion Google Mini Adeje 16 Oct 2008
Presentacion Google Mini Adeje 16 Oct 2008
 
Algunos recursos para desarrollo Web
Algunos recursos para desarrollo WebAlgunos recursos para desarrollo Web
Algunos recursos para desarrollo Web
 
Hojas de estilo CSS (Cascade Style Sheets)
Hojas de estilo CSS (Cascade Style Sheets)Hojas de estilo CSS (Cascade Style Sheets)
Hojas de estilo CSS (Cascade Style Sheets)
 
Extranet Parlamento
Extranet ParlamentoExtranet Parlamento
Extranet Parlamento
 

Último

redes informaticas en una oficina administrativa
redes informaticas en una oficina administrativaredes informaticas en una oficina administrativa
redes informaticas en una oficina administrativa
nicho110
 

Último (11)

Resistencia extrema al cobre por un consorcio bacteriano conformado por Sulfo...
Resistencia extrema al cobre por un consorcio bacteriano conformado por Sulfo...Resistencia extrema al cobre por un consorcio bacteriano conformado por Sulfo...
Resistencia extrema al cobre por un consorcio bacteriano conformado por Sulfo...
 
redes informaticas en una oficina administrativa
redes informaticas en una oficina administrativaredes informaticas en una oficina administrativa
redes informaticas en una oficina administrativa
 
investigación de los Avances tecnológicos del siglo XXI
investigación de los Avances tecnológicos del siglo XXIinvestigación de los Avances tecnológicos del siglo XXI
investigación de los Avances tecnológicos del siglo XXI
 
Avances tecnológicos del siglo XXI y ejemplos de estos
Avances tecnológicos del siglo XXI y ejemplos de estosAvances tecnológicos del siglo XXI y ejemplos de estos
Avances tecnológicos del siglo XXI y ejemplos de estos
 
PROYECTO FINAL. Tutorial para publicar en SlideShare.pptx
PROYECTO FINAL. Tutorial para publicar en SlideShare.pptxPROYECTO FINAL. Tutorial para publicar en SlideShare.pptx
PROYECTO FINAL. Tutorial para publicar en SlideShare.pptx
 
Guia Basica para bachillerato de Circuitos Basicos
Guia Basica para bachillerato de Circuitos BasicosGuia Basica para bachillerato de Circuitos Basicos
Guia Basica para bachillerato de Circuitos Basicos
 
EVOLUCION DE LA TECNOLOGIA Y SUS ASPECTOSpptx
EVOLUCION DE LA TECNOLOGIA Y SUS ASPECTOSpptxEVOLUCION DE LA TECNOLOGIA Y SUS ASPECTOSpptx
EVOLUCION DE LA TECNOLOGIA Y SUS ASPECTOSpptx
 
Avances tecnológicos del siglo XXI 10-07 eyvana
Avances tecnológicos del siglo XXI 10-07 eyvanaAvances tecnológicos del siglo XXI 10-07 eyvana
Avances tecnológicos del siglo XXI 10-07 eyvana
 
How to use Redis with MuleSoft. A quick start presentation.
How to use Redis with MuleSoft. A quick start presentation.How to use Redis with MuleSoft. A quick start presentation.
How to use Redis with MuleSoft. A quick start presentation.
 
Buenos_Aires_Meetup_Redis_20240430_.pptx
Buenos_Aires_Meetup_Redis_20240430_.pptxBuenos_Aires_Meetup_Redis_20240430_.pptx
Buenos_Aires_Meetup_Redis_20240430_.pptx
 
Innovaciones tecnologicas en el siglo 21
Innovaciones tecnologicas en el siglo 21Innovaciones tecnologicas en el siglo 21
Innovaciones tecnologicas en el siglo 21
 

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
  • 24. class Vector: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return 'Vector({}, {})'.format(self.x, self.y) def __add__(self, other): return Vector( self.x + other.x, self.y + other.y ) def __sub__(self, other): return Vector( self.x - other.x, self.y - other.y ) class Vector: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return 'Vector({}, {})'.format(self.x, self.y) def __add__(self, other): return Vector( self.x + other.x, self.y + other.y ) def __sub__(self, other): return Vector( self.x - other.x, self.y - other.y ) ejemplos/clases_04.py
  • 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
  • 48.
  • 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.
  • 52. Los pagos 1404.93 € 207.68 € 297.39 € 1816.42 € 153.56 € 1286.85 € 322.90 € 175.04 € 335.43 € 259.74 € 301.28 € 1384.43 €
  • 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
  • 55. facturacion_b.py (1) from decimal import Decimal import itertools ingresos = [ Decimal('4090.20'), Decimal('1910.00'), Decimal('1945.45'), ] pagos = [ Decimal('1404.93'), Decimal('207.68'), Decimal('297.39'), Decimal('1816.42'), Decimal('153.56'), Decimal('1286.85'), Decimal('322.9'), Decimal('175.04'), Decimal('335.43'), Decimal('259.74'), Decimal('301.28'), Decimal('1384.43'), ] from decimal import Decimal import itertools ingresos = [ Decimal('4090.20'), Decimal('1910.00'), Decimal('1945.45'), ] pagos = [ Decimal('1404.93'), Decimal('207.68'), Decimal('297.39'), Decimal('1816.42'), Decimal('153.56'), Decimal('1286.85'), Decimal('322.9'), Decimal('175.04'), Decimal('335.43'), Decimal('259.74'), Decimal('301.28'), Decimal('1384.43'), ]
  • 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)
  • 57. Solución 1816.42 + 153.56 + 1286.85 + 322.9 + 175.04 + 335.43 = 4090.20 1404.93 + 207.68 + 297.39 = 1910.00 259.74 + 301.28 + 1384.43 = 1945.45
  • 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
  • 61. Ejemplo Calcular los primeros 200 números que son divisibles por 5 y por 7
  • 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
  • 64. cubos de los 10 primeros números >>> def cube(x): return x*x*x ... >>> map(cube, range(1, 11)) [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000] >>> >>> def cube(x): return x*x*x ... >>> map(cube, range(1, 11)) [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000] >>>
  • 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
  • 68. Ejercicio: sumar los valores de una lista
  • 69. 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 >>>
  • 70. 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 >>> Suma(1,2) = 3 Suma(3,3) = 6 Suma(6,4) = 10 Suma(10,5) = 15 Suma(15,6) = 21 Suma(21,7) = 28 Suma(28,8) = 36 Suma(36,9) = 45 Suma(45,10) = 55
  • 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
  • 86. Suena raro... Una función … … puede devolver … … otra función
  • 87. No es tan raro, veamos un ejemplo >>> def dame_una_funcion_incremento(inc): ... def funcion_a_retornar(x): ... return x + inc ... return funcion_a_retornar ... >>> inc3 = dame_una_funcion_incremento(3) >>> inc3(6) 9 >>> inc47 = dame_una_funcion_incremento(47) >>> inc47(3) 50 >>> def dame_una_funcion_incremento(inc): ... def funcion_a_retornar(x): ... return x + inc ... return funcion_a_retornar ... >>> inc3 = dame_una_funcion_incremento(3) >>> inc3(6) 9 >>> inc47 = dame_una_funcion_incremento(47) >>> inc47(3) 50
  • 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