summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEkaitz Zarraga <ekaitz@elenq.tech>2019-12-02 13:51:37 +0100
committerEkaitz Zarraga <ekaitz@elenq.tech>2019-12-02 13:51:37 +0100
commit9e1515944ebfcdaaec533f0685d2dd9c9017d464 (patch)
treea9d8024e4443425695e476f180dee27e705a0712 /src
parentfb47609021e2a8890559862032c0b54c30b05829 (diff)
some protocols more
Diffstat (limited to 'src')
-rw-r--r--src/05_oop.md130
1 files changed, 130 insertions, 0 deletions
diff --git a/src/05_oop.md b/src/05_oop.md
index 5e8f2c4..b93ac87 100644
--- a/src/05_oop.md
+++ b/src/05_oop.md
@@ -596,8 +596,138 @@ True
### *Iterable*: `__next__` e `__iter__`
+El protocolo iterable permite crear objetos con los que es posible iterar en
+bucles `for` y otras estructuras. Por ejemplo, los archivos de texto en python
+soportan este protocolo, por lo que pueden leerse línea a línea en un bucle
+`for`.
+
+Igual que en el caso del protocolo `__len__`, que servía para habilitar la
+llamada a la función `len`, `__iter__` y `__next__` sirven, respectivamente,
+para habilitar las llamadas a `iter` y `next`.
+
+La función `iter` sirve para convertir el elemento a *iterable*, que es una
+clase que soporte el funcionamiento de la función `next`. Y `next` sirve para
+pasar al siguiente elemento de un iterable. Ejemplificado:
+
+``` python
+>>> l = [1,2,3]
+>>> next(l)
+Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+TypeError: 'list' object is not an iterator
+>>> it = iter(l)
+>>> it
+<list_iterator object at 0x7ff745723908>
+>>>
+>>> next(it)
+1
+>>> next(it)
+2
+>>> next(it)
+3
+>>> next(it)
+Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+StopIteration
+```
+
+La función `__next__` tiene un comportamiento muy sencillo. Si hay un próximo
+elemento, lo devuelve. Si no lo hay lanza la excepción `StopIteration`, para
+que la capa superior la capture.
+
+Fíjate que la lista por defecto no es un iterable y que se debe construir un
+elemento iterable desde ella con `iter` para poder hacer `next`. Esto se debe a
+que la función `iter` está pensada para restaurar la posición del cursor en el
+primer elemento y poder volver a iniciar la iteración.
+
+Sorprendentemente, este es el procedimiento de cualquier `for` en python. El
+`for` es una estructura creada sobre un `while` que construye iterables e
+itera sobre ellos automáticamente.
+
+Este bucle `for`:
+
+``` python
+for el in secuencia:
+ # hace algo con `el`
+```
+
+Realmente se implementa de la siguiente manera:
+
+``` python
+# Construye un iterable desde la secuencia
+iter_obj = iter(secuencia)
+
+# Bucle infinito que se rompe cuando `next` lanza una
+# excepción de tipo `StopIteration`
+while True:
+ try:
+ el = next(iter_obj)
+ # hace algo con `el`
+ except StopIteration:
+ break
+```
+
+Así que, si necesitas una clase con capacidad para iterarse sobre ella, puedes
+crear un pequeño iterable que soporte el método `__next__` y devolver una
+instancia nueva de éste en el método `__iter__`.
+
### *Creable*: `__init__`
+El método `__init__` es uno de los más usados e interesantes de esta lista, esa
+es la razón por la que ha aparecido en más de una ocasión durante este
+capítulo.
+
+El método `__init__` es a quién se llama al crear nuevas instancias de una
+clase y sirve para *ini*cializar las propiedades del recién creado objeto.
+
+Cuando se crean nuevos objetos, python construye su estructura en memoria,
+pidiéndole al sistema operativo el espacio necesario. Una vez la tiene, envía
+esa estructura vacía a la función `__init__` como primer argumento para que sea
+ésta la encargada de rellenarla.
+
+Como se ha visto en algún ejemplo previo, el método `__init__` (es un método,
+porque el objeto, aunque vacío, ya está creado) puede recibir argumentos de
+entrada adicionales, que serán los que la llamada al nombre de la clase reciba,
+a la hora de crear los nuevos objetos. Es muy habitual que el inicializador
+reciba argumentos de entrada, sobre todo argumentos con nombre, para que el
+programador que crea las instancias tenga la opción de inicializar los campos
+que le interesen.
+
+Volviendo a un ejemplo previo:
+
+``` python
+class Dog:
+ type = "canine"
+ def __init__(self, name):
+ self.name = name
+ def bark(self):
+ print("Woof! My name is " + self.name)
+
+bobby = Dog("Bobby") # Aquí se llama a __init__
+```
+
+El nombre del perro, `"Bobby"` será recibido por `__init__` en el argumento
+`name` e insertado al `self` mediante `self.name = name`. De este modo, esa
+instancia de `Dog`, `bobby`, tomará el nombre `Bobby`.
+
+> NOTA: En muchas ocasiones, el método `__init__` inicializa a valores vacíos
+> todas las posibles propiedades del objeto con el fin de que quien lea el
+> código de la clase sea capaz de ver cuáles son los campos que se utilizan en
+> un primer vistazo. Es una buena práctica listar todos los campos posibles en
+> `__init__`, a pesar de que no se necesite inicializarlos aún, con el fin de
+> facilitar la lectura.
+
+> NOTA: Quien tenga experiencia con C++ puede equivocarse pensando que
+> `__init__` es un constructor. Tal y como se ha explicado anteriormente, al
+> método `__init__` ya llega un objeto construido. El objetivo de `__init__` es
+> inicializar. En python el constructor, que se encarga de crear las instancias
+> de la clase, es la función `__new__`.
+
+> NOTA: Si creas una clase a partir de la herencia y sobreescribes su método
+> `__init__` es posible que tengas que llamar al método `__init__` de la
+> superclase para inicializar los campos asociados a la superclase. Recuerda
+> que puedes acceder a la superclase usando `super`.
+
### *Abrible* y *cerrable*: `__enter__` y `__exit__`
Que funcionen con el `with`