summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEkaitz Zarraga <ekaitz@elenq.tech>2019-12-02 20:18:11 +0100
committerEkaitz Zarraga <ekaitz@elenq.tech>2019-12-02 20:18:11 +0100
commita3bf4ff07300c368a5cbfd7d1a56c6dc7fd9c8c8 (patch)
treecfa561ee85944eb2099a9eba91fb8cfc5b4f7be8
parentc84d8f1dcf52162c07b082b198540ffd66df7c2e (diff)
OOP end
-rw-r--r--src/05_oop.md255
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.