diff options
-rw-r--r-- | src/05_oop.md | 255 |
1 files changed, 249 insertions, 6 deletions
diff --git a/src/05_oop.md b/src/05_oop.md index b93ac87..b2a21cf 100644 --- a/src/05_oop.md +++ b/src/05_oop.md @@ -730,21 +730,264 @@ instancia de `Dog`, `bobby`, tomará el nombre `Bobby`. ### *Abrible* y *cerrable*: `__enter__` y `__exit__` -Que funcionen con el `with` +Este protocolo permite que los objetos puedan ser abiertos y cerrados de forma +segura y con una sintaxis eficiente. Aunque no se van a listar en profundidad, +el objetivo de este punto es mostrar la sentencia `with` que se habilita +gracias a estos protocolos y mostrar cómo facilitan la apertura y cierre. -> context management protocol +El PEP 343[^pep343] muestra en detalle la implementación de la sentencia +`with`. Simplificándolo y resumiéndolo, `with` sirve para abrir elementos y +cerrarlos de forma automática. + +> NOTA: Los PEP (*Python Enhancement Proposals*) son propuestas de mejora para +> el lenguaje. Puedes consultar todos en la web de python. Son una fuente +> interesante de información y conocimiento del lenguaje y de programación en +> general. +> <https://www.python.org/dev/peps/> + +Pensando en, por ejemplo, la lectura de un archivo, se requieren varias etapas +para tratar con él, por ejemplo: + +``` python +f = open("file.txt") # apertura del fichero +f.read() # lectura +f.close() # cierre +``` + +Este método es un poco arcaico y peligroso. Si durante la lectura del fichero +ocurriera alguna excepción el fichero no se cerraría, ya que la excepción +bloquearía la ejecución del programa. Para evitar estos problemas, lo lógico +sería hacer una estructura `try-except` y añadir el cierre del fichero en un +`finally`. + +La sentencia `with` se encarga básicamente de hacer eso y facilita la escritura +de todo el proceso quedándose así: + +``` python +with f as open("file.txt"): # apertura + f.read() # en este cuerpo `f` está abierto + +# Al terminar el cuerpo, de forma normal o forzada, +# `f` se cierra. +``` + +Ahora bien, para que el fichero pueda ser abierto y cerrado automáticamente, +deberá tener implementados los métodos `__enter__` y `__exit__`. En el PEP 343 +se muestra la equivalencia entre la sentencia `with` y el uso de `__enter__`, +`__close__` y el `try-except`. + +[^pep343]: Puedes leer el contenido completo del PEP en: +<https://www.python.org/dev/peps/pep-0343/> ### *Llamable*: `__call__` Queda pendiente desde el capítulo sobre funciones, responder a lo que es un -*callable* o *llamable*. +*callable* o *llamable*. Una vez llegados a este punto, tiene una respuesta +fácil: un *llamable* es un objeto que soporta el protocolo correspondiente, +definido por el método `__call__`. + +Aunque pueda parecer sorprendente, las funciones en python también se llaman de +este modo, así que realmente son objetos que se llaman porque soportan este +protocolo. Es lógico, porque las funciones, recuerda el capítulo previo, pueden +guardar valores, como el contexto en el que se crean (*closure*). Las funciones +son meros *llamables* y como tales se comportan. -https://stackoverflow.com/questions/111234/what-is-a-callable +Llevado más allá, los tipos básicos de python están definidos en clases +también, lógicamente, pero pueden ser llamados para hacer conversiones tal y +como vimos en el capítulo sobre datos. Simplemente, soportan el protocolo +*llamable*. + +``` python +>>> class Dog: +... def __call__(self): +... print("Dog called") +... +>>> dog = Dog() +>>> dog() +Dog called +``` + +Ten en cuenta que el método `__call__` puede recibir cualquier cantidad de +argumentos como ya hemos visto en apartados anteriores, pero el primero será el +propio objeto que está siendo llamado, el `self` que ya conocemos. + +Resumiendo, el método `__call__` describe cómo se comporta el objeto cuando se +le aplican las paréntesis. ### *Accesible*: `__getitem__` y `__setitem__` +Tal y como el método anterior describía cómo se aplican las paréntesis a un +objeto, el protocolo que se muestra en este apartado describe el comportamiento +del objeto cuando se le aplican los corchetes. Recordando el capítulo sobre +datos, los corchetes sirven para acceder a valores de las listas, tuplas, +diccionarios y sets, que resultan ser también un tipo de objeto que describe +este comportamiento mediante el protocolo que tenemos entre manos. + +Cuando python encuentra que se está tratando de acceder a un campo de un objeto +mediante los corchetes llama automáticamente al método `__getitem__` y cuando +se intenta asociar un campo a un valor llama al método `__setitem__` del +objeto. + +El siguiente ejemplo muestra el protocolo en funcionamiento en una clase sin +funcionamiento alguno. Lo lógico y funcional sería utilizar estos dos métodos +para facilitar el acceso a campos de estas clases o para crear clases que +pudiesen sustituir a listas, tuplas, diccionarios o sets de forma sencilla. + +``` python +>>> class Dog: +... def __getitem__(self, k): +... print(k) +... def __setitem__(self, k, v): +... print(k, v) +... +>>> bobby = Dog() +>>> bobby["field"] +field +>>> bobby["field"] = 10 +field 10 +``` + +Fíjate en que reciben diferente cantidad de argumentos de entrada cada uno de +los métodos. El método `__setitem__` necesita indicar no sólo qué *item* desea +alterarse, sino su también su valor. + +### Ejemplo de uso + +Para ejemplificar varios de estos protocolos, tomamos como ejemplo una pieza de +código fuente que quien escribe este documento ha usado en alguna ocasión en su +trabajo como desarrollador. + +Se trata de un iterable que es capaz de iterar en un sistema de ficheros +estructurado en carpetas *año-mes-día* con la estructura `AAAA/MM/DD`. Este +código se creó para analizar datos que se almacenaban de forma diaria en +carpetas con esta estructura. Diariamente se insertaban fichero a fichero por +un proceso previo y después se realizaban análisis semanales y mensuales de los +datos. Esta clase permitía buscar por las carpetas de forma sencilla y obtener +rápidamente un conjunto de carpetas que procesar. + +El ejemplo hace uso del módulo `datetime`, un módulo de la librería estándar +que sirve para procesar fechas y horas. Por ahora, puedes ver la forma de +importarlo como una receta y en el siguiente capítulo la entenderás a fondo. El +funcionamiento del módulo es sencillo y puedes usar la ayuda para comprobar las +funciones que no conozcas. + +Te animo a que analices el comportamiento del ejemplo, viendo en detalle cómo +se comporta. Como referencia, fuera de la estructura de la clase, en las +últimas líneas, tienes disponible un bucle que puedes probar a ejecutar para +ver cómo se comporta. + +``` {.python .numberLines} +from datetime import timedelta +from datetime import date + +class dateFileSystemIterator: + + """ + Iterate over YYYY/MM/DD filesystems or similar. + """ + def __init__( self, start = date.today(), end = date.today(), + days_step = 1, separator = '/'): + self.start = start + self.current = start + self.end = end + self.separator = separator + self.step = timedelta( days = days_step ) + + def __iter__( self ): + self.current = self.start + return self + + def __next__( self ): + if self.current >= self.end: + raise StopIteration + else: + self.current += self.step + datestring = self.current - self.step + datestring = datestring.strftime( "%Y" \ + + self.separator \ + + "%m"+self.separator \ + +"%d") + return datestring + + def __repr__( self ): + out = self.current - self.step + tostring = lambda x: x.strftime("%Y" \ + + self.separator \ + + "%m" \ + + self.separator + "%d") + return "<dateFileSystemIterator: <Current: " \ + + tostring(self.current) + ">" \ + + ",<Start: " + tostring(self.start) + ">" \ + + ",<End: " + tostring(self.end) + ">" \ + + ",<Step: " + str(self.step) + ">" + + +it = dateFileSystemIterator(start = date.today() - timedelta(days=30)) +print(it) +for i in it: + print(i) +``` + +#### Ejercicio libre: `yield` y los generadores + +La parte de la iteración del ejemplo previo puede realizarse forma más breve +mediante el uso de la sentencia `yield`. Aunque no la trataremos, `yield` +habilita muchos conceptos interesantes, entre ellos los *generadores*. + +A continuación tienes un ejemplo de cómo resolver el problema anterior mediante +el uso de esta sentencia. Te propongo como ejercicio que investigues cómo +funciona buscando información sobre los *generadores* (*generator*) y la +propia sentencia `yield`. + +``` python +from datetime import datetime, timedelta + +def iterate_dates( date_start, date_end=datetime.today(), + separator='/', step=timedelta(days=1)): + date = date_start + while date < date_end: + yield date.strftime('%Y'+separator+'%m'+separator+'%d') + date += step +``` + +`yield` tiene mucha relación con las *corrutinas* (*coroutine*) que, aunque no +se tratarán en este documento, son un concepto muy interesante que te animo a +investigar. Si lo haces, verás que los generadores son un caso simple de una +corrutina. ## Lo que has aprendido -En el último apartado has aprendido colateralmente a abrir archivos, crearlos y -a editarlos! +Este capítulo también ha sido intenso como el anterior, pero te prometo que no +volverá a pasar. El interés principal de este capítulo es el de hacerte conocer +la programación orientada a objetos y enseñarte que en python lo inunda todo. +Todo son objetos. + +Para entenderlo has comenzado aprendiendo lo que es la programación orientada a +objetos, concretamente la orientada a clases, donde has visto por primera vez +los conceptos de identidad propia, comportamiento y estado. + +Desde ahí has saltado al fundamento teórico de la programación orientada a +objetos y has visitado la encapsulación, la herencia y el polimorfismo para +luego, una vez comprendidos, comenzar a definir clases en python. + +Esto te ha llevado a necesitar conocer qué es el argumento que suele llamarse +`self`, una excusa perfecta para definir qué son las variables y funciones de +clase y en qué se diferencian de las propiedades y métodos. + +Como la encapsulación no se había tratado en detalle aún, lo próximo que has +hecho ha sido zambullirte en los campos privados viendo cómo python los crea +mediante un truco llamado *name mangling* y su impacto en la herencia. + +Aunque en este punto conocías el comportamiento general de la herencia hacia +abajo, necesitabas conocerlo hacia arriba. Por eso, ha tocado visitar la +función `super` en este punto, función que te permite acceder a la superclase +de la clase en la que te encuentras. En lugar de contártela en detalle, se te +ha dado una pincelada sobre ella para que tú investigues cuando lo veas +necesario, pero que sepas por dónde empezar. + +Para describir más en detalle lo calado que está python de programación +orientada a objetos necesitabas un ejemplo mucho más agresivo: los protocolos. +A través de ellos has visto cómo python recoge las funcionalidades estándar y +te permite crear objetos que las cumplan. Además, te ha servido para ver que +**todo** en python es un objeto (hasta las clases lo son) y para ver formas +elegantes de resolver problemas comunes, como los iteradores, `with` y otros. |