summaryrefslogtreecommitdiff
path: root/es/05_oop.md
diff options
context:
space:
mode:
Diffstat (limited to 'es/05_oop.md')
-rw-r--r--es/05_oop.md66
1 files changed, 49 insertions, 17 deletions
diff --git a/es/05_oop.md b/es/05_oop.md
index 99e09d1..81fa3af 100644
--- a/es/05_oop.md
+++ b/es/05_oop.md
@@ -615,9 +615,10 @@ Igual que en el caso de `__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:
+La función `iter` sirve para convertir obtener un *iterador* de un *iterable*.
+Este *iterador* es un objeto que soporta el funcionamiento de la función
+`next`. Y `next` sirve para pasar al siguiente elemento de la iteración.
+Ejemplificado:
``` python
>>> l = [1,2,3]
@@ -645,41 +646,58 @@ 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.
+Fíjate que la lista, que es un elemento sobre el que se puede iterar, esto es,
+un *iterable*, no soporta `next` sino que necesita obtener un *iterador* a
+partir de ella mediante `iter` y es éste el que soporta el `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.
+Sorprendentemente, éste es el procedimiento de cualquier `for` en python. El
+`for` es una estructura creada sobre un `while` que obtiene el *iterador* e
+itera sobre él automáticamente.
Este bucle `for`:
``` python
-for el in secuencia:
+for el in iterable:
# hace algo con `el`
```
Realmente se implementa de la siguiente manera:
``` python
-# Construye un iterable desde la secuencia
-iter_obj = iter(secuencia)
+# Construye un iterador desde la secuencia
+iterador = iter(secuencia)
# Bucle infinito que se rompe cuando `next` lanza una
# excepción de tipo `StopIteration`
while True:
try:
- el = next(iter_obj)
+ el = next(iterador)
# 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__`.
+crear un pequeño *iterador* que soporte el método `__next__` y devolver una
+instancia nueva de éste en el método `__iter__` de tu clase.
+
+La diferencia entre el *iterable* y el *iterador* es importante: el *iterable*
+es un objeto sobre el que se puede iterar, y el *iterador* el objeto que se
+utiliza para iterar sobre el *iterable*. Es decir, el *iterador* es la
+herramienta que se usa para iterar sobre algo sobre lo que se puede iterar (un
+*iterable*). Esto permite separar conceptos de forma clara, permitiendo, como
+se introdujo antes, reiniciar el iterador cada vez que se crea un bucle nuevo,
+pero también permitiendo asignar diferentes modos de iteración para el mismo
+tipo de objeto. Imagina, por ejemplo, in iterador que salta los elementos
+impares de una colección.
+
+En la práctica, en casos sencillos, el propio *iterable* implementa la interfaz
+de su *iterador* y se devuelve a sí mismo en el método `__iter__`. De esta
+manera el programador no necesita escribir dos clases y se puede salir con la
+suya haciendo la mitad del trabajo, pero la realidad es que es interesante
+separar los conceptos, sobre todo para el caso más general.
### *Inicializable*
@@ -1026,6 +1044,20 @@ for i in it:
print(i)
```
+> Esta clase implementa tanto el iterable como su propio iterador, pero fíjate
+> que cuando se llama a `__iter__` reinicia la cuenta. Otra forma de hacer esto
+> podría ser con dos clases que diferencien bien los conceptos (*iterable* e
+> *iterador*), pero es quizás demasiado escribir para un caso tan sencillo.
+>
+> El método `__repr__` hace mucho uso de la concatenación de strings y de la
+> conversión de valores a string. Históricamente en python siempre ha habido
+> formas más elegantes de hacer esto. La más reciente (a partir de python 3.6)
+> es utilizar lo que se conoce como *formatted string literals* o *f-strings*.
+> Puedes leer más sobre ellos en el PEP 498[^pep498]
+
+[^pep498]: Puedes leer el contenido completo del PEP en:
+<https://peps.python.org/pep-0498/>
+
#### Ejercicio libre: generadores
La parte de la iteración del ejemplo previo puede realizarse forma más breve
@@ -1041,7 +1073,7 @@ propia sentencia `yield`.
from datetime import datetime, timedelta
def iterate_dates( date_start, date_end=datetime.today(),
- separator='/', step=timedelta(days=1)):
+ separator='/', step=timedelta(days=1) ):
date = date_start
while date < date_end:
yield date.strftime('%Y'+separator+'%m'+separator+'%d')