From 9e1515944ebfcdaaec533f0685d2dd9c9017d464 Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Mon, 2 Dec 2019 13:51:37 +0100 Subject: some protocols more --- src/05_oop.md | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) (limited to 'src/05_oop.md') 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 "", line 1, in +TypeError: 'list' object is not an iterator +>>> it = iter(l) +>>> it + +>>> +>>> next(it) +1 +>>> next(it) +2 +>>> next(it) +3 +>>> next(it) +Traceback (most recent call last): + File "", line 1, in +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` -- cgit v1.2.3