02 python Programación orientada a objetos y funcional

download 02 python Programación orientada a objetos y funcional

If you can't read please download the document

description

Taller Python IAC: Programación orientada a objetos y funcional con Python

Transcript of 02 python Programación orientada a objetos y funcional

  • 1. HEL LENGUAJE DE PROGRAMACINPYTHONJuan Ignacio Rodrguez de Lenjileon en twittereuribates @ gmail.comProgramacin orientada a objetosProgramacin funcional

2. Objetos y Clases Las clases permiten que podamos definirnuestros propios tipos de datos Las Clases definen las propiedases(atributos) y las capacidades ycomportamiento (mtodos) general delos nuevos tipos Un objeto se crea o instancia a partir deuna clase 3. Objetos Un objeto es una variable querepresenta un caso particular dentro delconjunto de posibles instancias de unaclase De la misma forma que podemosconsiderar al nmero 7 como unainstancia particular de la clase NumerosEnteros 4. Creacin de clases Palabra reservada class La clase ms sencilla que podemospensar es:>>> class X:... pass>>>>>> class X:... pass>>> Instanciamos un objeto usando elnombre de la clase como si fuera unafuncin:>>> x = X()>>> print(x)>>> x = X()>>> print(x) 5. La clase Point Inicializador: Mtodo con el nombreespecial __init__ No es el constructor, pero casiclass Point:def __init__(self, lat, lng):self.latitud = latself.longitud = lngx = Point(28.4779, -16.3118)print(x.latitud, x.longitud)class Point:def __init__(self, lat, lng):self.latitud = latself.longitud = lngx = Point(28.4779, -16.3118)print(x.latitud, x.longitud) 6. self? Se crea el objeto. Inmediatamente a continuacin, comohemos visto, se llama al inicializador los dos parmetros que usamos al crear alobjeto son los mismos valores que se pasanal mtodo inicializador con los nombres laty lng Pero De donde sale el primerparmetro, self? Y qu representa? 7. Para programadores de C++ o Java Para programadores de C++ o Java es lavariable "magica" this. En Python se prefiri esta forma porconsiderarla ms explicita. De igual manera, los atributos dentro dela funcin tienen que venir precedidospor el self., no hay alias mgicos paralos atributos de la instancia, para evitarla ambigedad. 8. Seguimos con self Empezemos por la segunda pregunta:self representa al propio objeto reciencreado Este primer parmetro es lo quediferencia a las funciones que yaconociamos de los mtodos: un mtodo siempre tienen como primerparmetro la instancia sobre la que estsiendo ejecutado. 9. Quien pone self ah Al definir mtodos para una clase, hayque reservar el primer parmetro para elpropio objeto Al llamar al mtodo desde la instancia,Python ya se ocupa de poner el valorcorrecto como primer parmetro La tradicin y la costumbre marcan queeste primer parmetro se llame self,pero en realidad no existe obligacin dehacerlo (pero es conveniente hacerlo,por legibilidad) 10. Herencia Para poder hablar de clases y objetoscon propidad es necesario que hayaalgn tipo de herencia La herencia nos permite definir una clasea base de refinar o modificar otra(herencia simple) u otras (herenciamltiple) 11. Herencia simple Si una clase A deriva o hereda de unaclase B (o tambin se dice que la clase Bes una superclase de A): Entonces la clase A dispondr, de entrada,de todos los atributos y mtodos de B Pero puede aadir ms atributos y mtodose incluso modificar o borrar los que haheredado. 12. Como declarar herencia en Python La forma de expresar esta herencia enpython es>>> class A(B):... pass>>> class A(B):... pass Si la clase modifique un mtodo que haheredado, se dice que ha reescrito osobreescrito (override) el mtodo. 13. Caractersticas de la herencia Como los objetos instanciados de A tienen losmismos atributos y mtodos que B, debenpoder ser ser usados en cualquier sitio dondese use una instacia de B Entre A y B hay una relacin 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 mtodo f() de B Si tenemos una lista con objetos de tipo A yde tipo B mezclados, podemos invocar sinmiedo el mtodo f() en todos ellos, con laseguridad de que en cada caso se invocar almtodo adecuado. Esta capacidad se llama polimorfismo (delgriego Mltiples Formas) 15. Mtodos o atributos privados No existen en Python Existe una convencin de uso, por la cualsi un atributo o mtodo empieza con elcarcter subrayado, ha de entenderseque: Es de uso interno No deberas jugar con l a no ser que sepasmuy bien lo que ests haciendo Si en un futuro tu cdigo deja de funcionarporque has usado ese atributo o mtodo, nopuedes culpar a nadie ms que a ti mismo 16. Beneficios de usar clases/objetos Reducir el tamao del cdigo evitandorepeticiones Si organizamos las herencias correctamenteen jerarquias, de mas genricas a msespecficas, podemos compatibilizar elcdigo comn de las primeras con el msespecfico de las ltimas Encapsulamiento Polimorfismo Delegacin de responsabilidades 17. super Que pasa si A sobreescribe un mtodo de B, peroaun as ha de invocarlo? En realidad es un caso muy comn, A quiere hacerlo mismo que B, y un poquito ms. Desde Python 2.2 hay una funcin super() quenos ayuda a invocar el cdigo 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 unainstancia de una clase en particular, o dealguna de sus subclases issubclass(objeto, clase) nos devolver verdadero si el objeto es unainstancia de una subclase de la claseindicada. 19. Sobrecarga de operadores Se puede, como en C++, sobreescribirlos operadores (operadores aritmticos,acceso por ndices, etc...) mediante unasintaxis especial Los mtodos y atributos que empiezan yacaban con un doble signo de subrayadotiene por lo general un significadoespecial. 20. Sobrecarga de operadores: len Si en nuestra clase definimos un mtodo__len__(), podemos hacer que lasinstancias de esa clase puedan serusadas con la funcin len() Vase ejemplos/clases_02.py 21. Sobrecarga de operadores: ndices Si a una clase le aadimos los mtodos__setitem__ y __getitem__ podemoshacer que se comporte como si fuerauna contenedor accesible mediante lasoperaciones 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 caradef __getitem__(self, index):if 0 > 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 cdigocon excepciones Los programas en C suelen consistir enuna serie de llamadas a funcionesintercaladas con comprobaciones deresultados Con excepciones: La gestin de los errores no se entrometecon la funcin del algoritmo Est centralizada y aparte 32. Elevar excepciones Podemos elevar nosotros mismosexcepciones, usando la palabrareservada raise Podemos definir nuestras propiasexcepciones Derivadas de Exception Jerarqua de excepciones 33. Finally Clusula final, que se ejecutar siempre Se hayan producido o no excepciones Uso habitual: Liberacin de recursos Operaciones de limpieza Cualquier cdigo que tenga que ejecutarse"si si" 34. Gestores de contexto: with with nos permite "envolver" un bloque decdigo con operaciones a ejecutar antes ydespus del mismo Simetra en las operaciones Garanta de ejecucin Se pueden anidar Ejemplos ficheros: open/close memoria: malloc/free 35. Ms clarotry:f = open(fichero.datos, r)# proceso el ficheron = len(f.readlines())finally:f.close()try:f = open(fichero.datos, r)# proceso el ficheron = 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 quehacer antes y despus de usar otroobjeto El generador de contexto de file sabeque hay que cerrar el archivo Imposible olvidarse de cerrarlo 37. Como funciona with (2)1) Evaluacin y obtencin del gestor decontexto2) Se carga __exit__()3) Se ejecuta __enter__() (si devuelve unvalor y se ha usado as, se asigna a lavariable)4) Se ejecuta el bloque5) Se llama a __exit__() (con informacinde errores, si hubiera) 38. Iteradores Nuestras clases y objetos pueden seriterables Como funciona for internamente: Se llama a iter() pasndole lo que quieraque sea que vamos a iterar Obtenemos un iterador, es decir, un objetocon un metodo next() Llamamos a next() repetidas veces... Hasta que termina: se eleva StopIteration 39. Ejemplo>>> s = abc>>> it = iter(s)>>> it>>> it.next()a>>> it.next()b>>> it.next()c>>> it.next()Traceback (most recent call last):File "", line 1, in ?it.next()StopIteration>>> s = abc>>> it = iter(s)>>> it>>> it.next()a>>> it.next()b>>> it.next()c>>> it.next()Traceback (most recent call last):File "", line 1, in ?it.next()StopIteration 40. Es fcil aadir iterabilidad Definir un mtodo con el nombre__iter__() que devuelva un objeto Este objeto debe implementar un mtodonext() Cada vez que se llame a next(), este debedevolver el siguiente elemento A no ser que no queden, entonces, elevaStopIteration Nuestra clase puede ser su propio iterador:basta con definir next() y en __iter__devolver self 41. Ejercicio Crear una clase CuentaAtras, que seaiterable y que, ejem, cuente hacia atrshasta llegar al cero. 42. Solucinclass CuentaAtras:def __init__(self, tope):self.tope = topedef __iter__(self):self.counter = self.topereturn selfdef next(self): return self.__next__() # python 2.7def __next__(self):result = self.counterself.counter -= 1if result < 0:raise StopIterationreturn resultclass CuentaAtras:def __init__(self, tope):self.tope = topedef __iter__(self):self.counter = self.topereturn selfdef next(self): return self.__next__() # python 2.7def __next__(self):result = self.counterself.counter -= 1if result < 0:raise StopIterationreturn result 43. Generadores Forma sencilla y potente de crear iteradores Como una funcin, pero devuelven resultados conyield, en vez de return Cada vez que se llama a next(), el generadorcontinua a partir de donde se qued Recuerda su estado: valores de las variables,ltima lnea 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)...543210>>>>>> def cuenta_atras(n):... while n >= 0:... yield n... n -= 1...>>> for i in cuenta_atras(5): print(i)...543210>>> 45. Ventajas Igual potencia que un iterador Solo por usar yield ya se sabe que esun generador Normalmente ms fciles de escribir Generacin automtica de next() y de__iter__() Eleva automticamente StopIteration 46. Ejemplos degeneradores/iteradores El mdulo os.path, como veremos msadelante, tiene una funcin walk, que esun generador que nos permite recorrerun rbol de directorios El mdulo itertools define una funcioncombinations que nos da lascombinaciones de m elementostomandos de n en n 47. Ejemplo de combinaciones Combinaciones tomando los cuatroelementos 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. La Fiscalia Anticorrupcinnos pide ayuda 49. Ajustar cuentas Estan investigando un casode un ex-tesorero y unadoble facturacin Hay una serie de ingresospor un lado Y una serie de pagos por otro Demostrar que la serie depagos se corresponden,sumadas, con los ingresos. 50. Los ingresos1910.00 4090.20 1945.45 51. Los pagos1404.93 207.68 297.39 1816.42 153.56 1286.85 322.90 175.04 335.43 259.74 301.28 1384.43 52. Como podemos hacerlo? Empezar por uno de los ingresos Tomar las combinaciones de pagos ytomarlas de una en una. Ver si sumadascoinciden con el ingreso Tomar las combinaciones de pagos ytomarlas de dos en dos. Ver si sumadascoinciden con el ingreso Seguir asi buscando combinaciones hastaque una coincida. Cuando se encuentra,retirar esos pagos de la lista de pagos yseguir con el siguiente ingreso 53. Herramientas Conseguir las combinaciones usandoitertoos.combinations. La funcin sum() nos suma loselementos de una secuencia Empezar con una versin minima delproblema: 2 ingresos, 4 pagos, porejemplo. Solucin en: ejemplos/facturacion_b.py 54. facturacion_b.py (1)from decimal import Decimalimport itertoolsingresos = [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 Decimalimport itertoolsingresos = [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),] 55. facturacion_b.py (2)for ingreso in ingresos:solucion = Nonefor size in range(1, len(pagos)+1):# Probando combinaciones de size elementosfor a_probar in itertools.combinations(pagos, size):if sum(a_probar) == ingreso:print(Encontrada una solucin:)solucion = tuple(a_probar)print(*[{0:f}.format(d) for d in solucion],sep= + ,end= = )print(ingreso)breakif solucion:for pago in solucion:pagos.remove(pago)for ingreso in ingresos:solucion = Nonefor size in range(1, len(pagos)+1):# Probando combinaciones de size elementosfor a_probar in itertools.combinations(pagos, size):if sum(a_probar) == ingreso:print(Encontrada una solucin:)solucion = tuple(a_probar)print(*[{0:f}.format(d) for d in solucion],sep= + ,end= = )print(ingreso)breakif solucion:for pago in solucion:pagos.remove(pago) 56. Solucin1816.42 + 153.56+ 1286.85 + 322.9+ 175.04 + 335.43 = 4090.201404.93 + 207.68 + 297.39 = 1910.00259.74 + 301.28 + 1384.43 = 1945.45 57. Programacin funcional Las funciones solo son otro tipo devariable Todo lo que se puede hacer con unavariable, se puede hacer con una funcin: funciones como parmetros funciones dentro de estructuras de datos funciones como resultados Las funciones son objetos de primeraclase 58. Funciones Lambda Crear pequeas funciones annimas lambda : Funcin que suma los dos parmetrosque se le pasan: lambda(x,y): x+y No hace falta especificar return Azucar sintctico para una definicin defuncin normal 59. filter Primer parmetro: una funcin Segundo parmetro: una secuencia Devuelve: otra secuencia en la que seestan slo aquellos valores de lasecuencia original para los que elresultado de aplicarles la funcin esTrue 60. EjemploCalcular los primeros 200nmeros que son divisiblespor 5 y por 7 61. los primeros 200 nmerosque 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)...3570105140175>>>>>> def div57(x):... return x % 5 == 0 and x % 7 == 0...>>> for i in filter(div57, range(1, 201)):... print(i)...3570105140175>>> 62. map Primer parmetro: una funcin Segundo parmetro: una secuencia Devuelve: otra secuencia, compuestapor los resultados de llamar a la funcinen cada uno de los elementos de lasecuencia original 63. cubos de los 10 primeros nmeros>>> 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]>>> 64. map (2) Podemos pasar ms de una secuencia La funcin pasada como parmetrodebe aceptar tantos parmetros comosecuencias haya Ejercicio: media de los datos de otrasdos listas 65. 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]>>> 66. reduce Primer parmetro: una funcin Segundo parmetro: una secuencia Devuelve: un nico valor la funcin que se pasa como parmetro tieneque aceptar dos valores, y retornar uno Se calcula el resultado de aplicar la funcin a losdos primeros valores de la secuencia A continuacin, se aplica de nuevo la funcin,usando como parmetros el dato anterior y altercer elemento de la secuencia As hasta acabar la secuencia original 67. Ejercicio: sumar los valoresde una lista 68. Ejercicio: sumar los valoresde 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>>> 69. Ejercicio: sumar los valoresde 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) = 3Suma(3,3) = 6Suma(6,4) = 10Suma(10,5) = 15Suma(15,6) = 21Suma(21,7) = 28Suma(28,8) = 36Suma(36,9) = 45Suma(45,10) = 55 70. Ejercicio: sumar los valoresde 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 comn que yaexiste una funcin incorporada para ello:sum(seq), que funciona exactamente igual, peroms rpido al estr implementada en C. 71. Compresin de listas Forma muy expresiva de crear listas Usos comunes Crear una lista cuyos elementos son resultadode aplicar una serie de operaciones a otrasecuencia Crear una sebsecuencia de aquellos elementosque cumplan una determinada condicin En resumen, nos permiten hacer lo mismoque map o filter, pero de forma mslegible. 72. 10 Nmeros cuadrados Podemos crear una lista con loscuadrados de los 10 primeros nmerosas:>>> 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]>>> 73. 10 Nmeros cuadrados O as, con mapsquares = map(lambda x: x**2, range(10))squares = map(lambda x: x**2, range(10)) 74. 10 Nmeros cuadrados Con comprensin de listas es aun msfcilsquares = [x**2 for x in range(11)]squares = [x**2 for x in range(11)] 75. Anatoma de una C.L. Corchete [ Expresin Clusula for Cero, una o ms clusulas if Corchete ] 76. Ejercicio Cules de los primeros 1500 nmerosenteros cumplen la condicin de que sucubo acaba en 272? str() convierte un nmero en texto endswith() es un metodo de los textos quedevuelve True si la cadena de texto sobre laque se aplica acaba en el texto indicadocomo parmetro: hola.endswith(a) True 77. 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] 78. Expresiones generadoras Muy similar a una conprensin de lista Pero devuelve un generador, no una lista La sintaxis es idntica, sustituyendo loscorchetes por parntesis con la lista obtenemos todos los elementosya generados (y, por tanto, consumiendomemoria) El generador nos ir dando los valores deuno en uno (lazy evaluation) 79. 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 at 0xb74588ec>>>> s.next()0>>> for i in s: print(i)...14[...]81100>>>>>> 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 at 0xb74588ec>>>> s.next()0>>> for i in s: print(i)...14[...]81100>>> 80. Comprensin de diccionarios Crear diccionarios a partir de otrasfuentes de datos Sintaxis similar, pero cambiandocorchetes/parntesis por llaves: {} La expresin tienen que tener laforma : 81. 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}>>> 82. Conprensin de conjuntos Definir un conjunto a partir de otrosvalores Igual que con los diccionarios, pero laexpresin no va en la forma:, sino como unaexpresin simple NO se puede crear as un diccionariovacio (Creara un diccionario) 83. Ejemplo>>> s = {a, b, c}>>> sset([a, c, b])>>> s = {str(x**2) for x in range(7)}>>> type(s)>>> sset([25, 16, 36, 1, 0, 4, 9])>>>>>> s = {a, b, c}>>> sset([a, c, b])>>> s = {str(x**2) for x in range(7)}>>> type(s)>>> sset([25, 16, 36, 1, 0, 4, 9])>>> 84. Decoradores Funciones como objetos de primer nivel Las funciones se pueden almacenar,pasar como parmetros... o ser devueltas como resultado deuna funcin Una funcin puede devolver otra funcin 85. Suena raro...Una funcin puede devolver otra funcin 86. 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 87. Qu es un decorador? Sabiendo que esto es posible, undecorador es: Una funcin que acepta como parmetrouna funcin, y devuelve otra funcin, quenormalmente sustituir a la original. Es decir, un decorador nos permitemodificar una funcin (Normalmentehaciendo algo antes, o despus, o ambascosas) 88. Referencia friki totalmente gratuita 89. Para qu sirve un decorador? El uso de decoradores se enfoca aresolver el siguiente problema: Tenemos un conjunto de funciones Queremos que todas ellas hagan una nuevacosa, algo por lo general ajeno al propiocomportamiento de la funcin, y que todaslo hagan por igual. En otras palabras, queremos aadir unafuncionalidad horizontal. 90. Ejemplo de situacin Supongamos que tenemos un conjuntode funciones a(), b(),..., z(), cada unade ellas con sus parmetros,particularidades, etc... Queremos ahora, con el mnimo trabajoposible, que cada funcin escriba en unfichero log cuando empieza a trabajar ycuanto termina. 91. Opcin A (de A lo bruto)Reescribir cada una de las funcionesdef a():# cdigo de adef a():# cdigo de aPasar de esto:def a():with open(/tmp/log.txt, a) as log:log.write(Empieza la funcin an)# codigo de awith open(/tmp/log.txt, a) as log:log.write(Acaba la funcin an)def a():with open(/tmp/log.txt, a) as log:log.write(Empieza la funcin an)# codigo de awith open(/tmp/log.txt, a) as log:log.write(Acaba la funcin an)A esto: 92. Pegas de la opcin A Sencillo, pero trabajoso Hay que reescribir mucho cdigo El tamao del cdigo aumenta La lgica de las funciones quedadifuminada con todas esas llamadas aescribir el log Si queremos cambiar la informacin dellog, (incluir fecha y hora, p.e.) hay quevolver a modificar todas las funciones 93. Opcin D (De decoradores) Intenta solucionar estos problemas Un decorador coge las funcin original,(a(), b(),..., z() en nuestro caso), lamodifica y la reemplaza Ahora, cuando se llama a a(), se invocaen realidad a nuestra versin modificada(que a su vez invocar a la a() original) 94. Decorador logged Para el ejemplo de log, primero creamosuna funcin decoradora, que llamaramoslogged() Para simplificar, en vez de escribir a unfichero log nos limitaremos a hacer dosprints, uno antes de que empieze lafuncin y otro despus 95. Cdigo de loggeddef logged(func):def inner(* args, **kwargs):print(Empieza la funcin {}.format(func.__name__))func(*args, **kwargs)print(Termina la funcin {}.format(func.__name__))return innerdef logged(func):def inner(* args, **kwargs):print(Empieza la funcin {}.format(func.__name__))func(*args, **kwargs)print(Termina la funcin {}.format(func.__name__))return inner 96. Aplicacin del decoradordef a(): print(Soy a())def b(): print(Soy b())b = logged(b)@loggeddef c(): print(Soy c())@loggeddef 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)@loggeddef c(): print(Soy c())@loggeddef d(msg):print(Soy d y digo: {}.format(msg))a()b()c()d(Hola, mundo) 97. Aplicacin de decoradoresLa forma:@loggeddef c(): print(Soy c())@loggeddef c(): print(Soy c())Es azucar sintctico para:def c(): print(Soy c())c = logged(c)def c(): print(Soy c())c = logged(c)La forma ms cmoda de aplicar eldecorador es poner el smbolo @ y elnombre del decorador antes de la definicinde la funcin 98. Ventaja de los decoradores Hay que tocar el cdigo de cada funcin, si, pero elcambio es mnimo: aadir el decorador con elsimbolo @ El cdigo no se repite. No hay aumento apreciablede tamao del mismo El cdigo interno de las funciones decoradas no seve perturbado por la nueva funcionalidad. Podemos aadir nuevas caractersticas a lasfunciones "logeadas" modificando solo una cosa: eldecorador